views:

309

answers:

4

I want to start a new thread using a C function, not an objective-C method. I tried

[NSThread detachNewThreadSelector: @selector(func) toTarget: nil withObject: id(data)];

where I have

void func(void *data) {
   // ...
}

and data is a void *, but I get a runtime crash in objc_msgSend, called from

-[NSThread initWithTarget:selector:object:]

What can I do instead? Is it even possible?

+3  A: 

Create an Objective-C class with a method that simply calls that function. Take the selector of that method and pass it to NSThread API.

Mehrdad Afshari
OK, yeah, I mean, without doing that. In other words, I want to know if I'm missing some syntax
Jesse Beder
No NSThread calls a method so you have no choice.
Mark
I don't think there's a simple syntactical way to do that. A selector is used to send a message to an object. Sending a message is not identical to simply calling a function. The underlying mechanisms are a bit different.
Mehrdad Afshari
I was hoping it would wrap the C function automagically if it had to.
Jesse Beder
+3  A: 

Well, I'm not sure if it's possible, but keep in mind that every Objective-C method has two implicit/hidden arguments, self and _cmd. An IMP is usually typedef'd like this:

typedef id (*IMP)(id,SEL,...);

If you want to jerry-rig methods and selectors, you need to have a method that looks like that:

void func (id self, SEL _cmd, void *firstParameter);

But even after that, you need to register a selector name with the runtime, then you need to associate that selector with the method, but this is done on a class-by-class basis (i.e. classes can have different implementations of the same selector name), so you at least need to have a dummy class.

It is much, much simpler just to create a dummy class and dummy instance of that class than call the various runtime API just to get NSThread to invoke a single C function.

dreamlax
+5  A: 

Roll your own:

// In some .h file.  #import to make the extension methods 'visible' to your code.
@interface NSThread (FunctionExtension)
+(void)detachNewThreadByCallingFunction:(void (*)(void *))function data:(void *)data;
-(id)initWithFunction:(void (*)(void *))function data:(void *)data;
@end

// In some .m file.
@implementation NSThread (FunctionExtension)

+(void)startBackgroundThreadUsingFunction:(id)object
{
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

  void (*startThreadFunction)(void *) = (void (*)(void *))[[object objectForKey:@"function"] pointerValue];
  void *startThreadData               = (void *)          [[object objectForKey:@"data"] pointerValue];

  if(startThreadFunction != NULL) { startThreadFunction(startThreadData); }

  [pool release];
  pool = NULL;
}

+(void)detachNewThreadByCallingFunction:(void (*)(void *))function data:(void *)data
{
  [[[[NSThread alloc] initWithFunction:function data:data] autorelease] start];
}

-(id)initWithFunction:(void (*)(void *))function data:(void *)data
{
  return([self initWithTarget:[NSThread class] selector:@selector(startBackgroundThreadUsingFunction:) object:[NSDictionary dictionaryWithObjectsAndKeys:[NSValue valueWithPointer:function], @"function", [NSValue valueWithPointer:data], @"data", NULL]]);
}

@end

NOTE: I wrote the above code and here by place it in the public domain. (sometimes the lawyers like this kind of stuff) It is also completely untested!

You can always remove the NSAutoreleasePool bits if you can guarantee that the thread entry function also creates one... but it's harmless, has no speed penalty what-so-ever, and makes calling arbitrary C functions that much more simpler. I'd say just keep it there.

And you can use it like so:

void bgThreadFunction(void *data)
{
  NSLog(@"bgThreadFunction STARTING!! Data: %p", data);
}

-(void)someMethod
{
  // init and then start later...
  NSThread *bgThread = [[[NSThread alloc] initWithFunction:bgThreadFunction data:(void *)0xdeadbeef] autorelease];
  // ... assume other code/stuff here.
  [bgThread start];

  // Or, use the all in one convenience method.
  [NSThread detachNewThreadByCallingFunction:bgThreadFunction data:(void *)0xcafebabe];
}

When run:

2009-08-30 22:21:12.529 test[64146:1303] bgThreadFunction STARTING!! Data: 0xdeadbeef
2009-08-30 22:21:12.529 test[64146:2903] bgThreadFunction STARTING!! Data: 0xcafebabe
johne
+1 Obj-C categories are very useful sometimes.
kaizer.se
Good idea, thanks!
Jesse Beder
+1  A: 

If you don't need NSThread stuff, you can also start a thread with direct POSIX interface.

mouviciel