Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can a C function be used as a selector in Cocoa?

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?

like image 300
Jesse Beder Avatar asked Aug 30 '09 22:08

Jesse Beder


4 Answers

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
like image 79
johne Avatar answered Oct 29 '22 13:10

johne


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.

like image 25
mmx Avatar answered Oct 29 '22 14:10

mmx


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.

like image 3
dreamlax Avatar answered Oct 29 '22 12:10

dreamlax


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

like image 1
mouviciel Avatar answered Oct 29 '22 12:10

mouviciel