Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -16,10 +16,11 @@ OFNumber.m \ OFObject.m \ ${OFPLUGIN_M} \ OFString.m \ OFTCPSocket.m \ + OFThread.m \ OFXMLFactory.m \ ${ASPRINTF_C} INCLUDESTMP = ${SRCS:.c=.h} INCLUDES = ${INCLUDESTMP:.m=.h} \ Index: src/OFExceptions.h ================================================================== --- src/OFExceptions.h +++ src/OFExceptions.h @@ -581,5 +581,17 @@ /** * \return The errno from when the exception was created */ - (int)errNo; @end + +/** + * An OFException indicating that joining the thread failed. + */ +@interface OFThreadJoinFailedException: OFException {} +@end + +/** + * An OFException indicating that the thread has been canceled. + */ +@interface OFThreadCanceledException: OFException {} +@end Index: src/OFExceptions.m ================================================================== --- src/OFExceptions.m +++ src/OFExceptions.m @@ -762,5 +762,32 @@ - (int)errNo { return err; } @end + +@implementation OFThreadJoinFailedException +- (const char*)cString +{ + if (string != NULL) + return string; + + asprintf(&string, "Joining a thread of class %s failed! Most likely, " + "another thread already waits for the thread to join.", + [class name]); + + return string; +} +@end + +@implementation OFThreadCanceledException +- (const char*)cString +{ + if (string != NULL) + return string; + + asprintf(&string, "The requested action cannot be performed because " + "the thread of class %s was canceled!", [class name]); + + return string; +} +@end ADDED src/OFThread.h Index: src/OFThread.h ================================================================== --- src/OFThread.h +++ src/OFThread.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2008 - 2009 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of libobjfw. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE included in + * the packaging of this file. + */ + +#ifndef _WIN32 +#include +#else +#include +#endif + +#import "OFObject.h" + +/** + * The OFThread class provides portable threads. + * + * To use it, you should create a new class derived from it and reimplement + * main. + */ +@interface OFThread: OFObject +{ + id object; +#ifndef _WIN32 + pthread_t thread; +#else + HANDLE thread; + +@public + id retval; +#endif +} + +/** + * \param obj An object that is passed to the main method + * \return A new, autoreleased thread + */ ++ threadWithObject: (id)obj; + +/** + * \param obj An object that is passed to the main method + * \return An initialized OFThread. + */ +- initWithObject: (id)obj; + +/** + * The main routine of the thread. You need to reimplement this! + * + * It can access the object passed to the threadWithObject or initWithObject + * method using the instance variable named object. + * + * \return The object the join method should return when called for this thread + */ +- (id)main; + +/** + * Joins a thread. + * + * \return The object returned by the main method of the thread. + */ +- join; +@end ADDED src/OFThread.m Index: src/OFThread.m ================================================================== --- src/OFThread.m +++ src/OFThread.m @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2008 - 2009 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of libobjfw. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE included in + * the packaging of this file. + */ + +#import "config.h" + +#import "OFThread.h" +#import "OFExceptions.h" + +#ifndef _WIN32 +static void* +call_main(void *obj) +{ + return [(OFThread*)obj main]; +} +#else +static DWORD WINAPI +call_main(LPVOID obj) +{ + /* Windows does not support returning a pointer. Nasty workaround. */ + ((OFThread*)obj)->retval = [(OFThread*)obj main]; + + return 0; +} +#endif + +@implementation OFThread ++ threadWithObject: (id)obj +{ + /* + * This is one of the rare cases where the convenience method should + * use self instead of the class. + * + * The reason is that you derive from OFThread and reimplement main. + * If OFThread instead of self would be used here, the reimplemented + * main would never be called. + */ + return [[[self alloc] initWithObject: obj] autorelease]; +} + +- initWithObject: (id)obj +{ + Class c; + + self = [super init]; + object = obj; + +#ifndef _WIN32 + if (pthread_create(&thread, NULL, call_main, self)) { +#else + if ((thread = + CreateThread(NULL, 0, call_main, self, 0, NULL)) == NULL) { +#endif + c = isa; + [super free]; + @throw [OFInitializationFailedException newWithClass: c]; + } + + return self; +} + +- main +{ + return nil; +} + +- join +{ +#ifndef _WIN32 + void *ret; + + if (pthread_join(thread, &ret)) + @throw [OFThreadJoinFailedException newWithClass: isa]; + + if (ret == PTHREAD_CANCELED) + @throw [OFThreadCanceledException newWithClass: isa]; + + return (id)ret; +#else + if (WaitForSingleObject(thread, INFINITE)) + @throw [OFThreadJoinFailedException newWithClass: isa]; + + CloseHandle(thread); + thread = INVALID_HANDLE_VALUE; + + return retval; +#endif +} + +- free +{ + /* + * No need to handle errors - if canceling the thread fails, we can't + * do anything anyway. Most likely, it finished already or was already + * canceled. + */ +#ifndef _WIN32 + pthread_cancel(thread); +#else + if (thread != INVALID_HANDLE_VALUE) { + TerminateThread(thread, 1); + CloseHandle(thread); + } +#endif + + return [super free]; +} +@end Index: tests/Makefile ================================================================== --- tests/Makefile +++ tests/Makefile @@ -6,9 +6,10 @@ OFDictionary \ OFHashes \ ${OFPLUGIN} \ OFString \ OFTCPSocket \ + OFThread \ OFList \ OFXMLFactory include ../buildsys.mk ADDED tests/OFThread/Makefile Index: tests/OFThread/Makefile ================================================================== --- tests/OFThread/Makefile +++ tests/OFThread/Makefile @@ -0,0 +1,23 @@ +PROG_NOINST = ofthread${PROG_SUFFIX} +SRCS = OFThread.m + +include ../../buildsys.mk +include ../../extra.mk + +CPPFLAGS += -I../../src -I../.. +LIBS := -L../../src -lobjfw ${LIBS} + +.PHONY: run + +all: run +run: ${PROG_NOINST} + rm -f libobjfw.so.1 libobjfw.so.1.0 libobjfw.dll libobjfw.dylib + ln -s ../../src/libobjfw.so libobjfw.so.1 + ln -s ../../src/libobjfw.so libobjfw.so.1.0 + ln -s ../../src/libobjfw.dll libobjfw.dll + ln -s ../../src/libobjfw.dylib libobjfw.dylib + LD_LIBRARY_PATH=.$${LD_LIBRARY_PATH+:}$$LD_LIBRARY_PATH \ + DYLD_LIBRARY_PATH=.$${DYLD_LIBRARY_PATH+:}$$DYLD_LIBRARY_PATH \ + ${TEST_LAUNCHER} ./${PROG_NOINST}; EXIT=$$?; \ + rm -f libobjfw.so.1 libobjfw.so.1.0 libobjfw.dll libobjfw.dylib; \ + exit $$EXIT ADDED tests/OFThread/OFThread.m Index: tests/OFThread/OFThread.m ================================================================== --- tests/OFThread/OFThread.m +++ tests/OFThread/OFThread.m @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2008 - 2009 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of libobjfw. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE included in + * the packaging of this file. + */ + +#import "config.h" + +#include + +#import "OFThread.h" +#import "OFConstString.h" + +@interface MyThread: OFThread +@end + +@implementation MyThread +- main +{ + if ([object isEqual: @"foo"]) + return @"successful"; + + return @"failed"; +} +@end + +int +main() +{ + MyThread *t = [MyThread threadWithObject: @"foo"]; + + if (![[t join] isEqual: @"successful"]) { + puts("Test failed!"); + return 1; + } + + puts("Test successful!"); + return 0; +}