Borkware Miniblog

September 13, 2007

C Callbacks in ObjC

Filed under: amosxp, irc, programming — Mark Dalrymple @ 11:23 am

It’s interesting to see common subjects come up in the IRC channels. When someone unfamiliar joins and asks the question “how do I put a method into a function pointer?”, 9 times out of 10 they’re actually wanting to use Cocoa with an existing C API that uses callbacks. A much better question would be “I’m using a C API that uses callbacks with Cocoa, how do I make it work?”

The Answer

You can’t use Objective-C methods as callbacks. Stop trying, you’ll just make yourself angrier. You have to bounce off of a regular C function, and use any “userData” or “info” mechanism in your C API to pass a pointer to your object.

So assuming you have a call SetDrawingCallback, which takes a function, but you actually want to draw using your objecty-goodness OblateSphereoid object, you would do something like this:

BWOblateSphereoid *egg = [[BWOblateSphereoid alloc] init];

SetDrawingCallback (drawingContext, /* context that wants to draw */
                    drawEggThunk, /* the function that the context will call */
                    egg);  /* arbitrary userData pointer */

Then in your callback function:

void drawEggThunk (DrawingContext *context, Rect areaToDraw, void *userData)
{
    BWOblateSphereoid *dealie = (BWOblateSphereoid *)userData;
    [dealie drawAnEggInRect: areaToDraw];
} // drawEggThunk

You are allowed to call Objective-C methods in a C function. You just need to make sure your file is compiled as Objective-C, whether you have a .m extension, or tell Xcode to compile it as Objective-C explicitly. If your callback API doesn’t let you pass in a userData pointer (a rock to hide your data under), you’ll have to put your object into some global location for your C function to find.

If you need to poke inside of the object directly, you can put this function inside of the @implementation block of your class, and then you can access instance variables with the C arrow operator. But that’s kind of naughty, so to preserve your Purity of Essence you should be using method calls on your object. End of Sermon. Here’s the evil:

@implementation OblateSphereoid

void drawEggThunk (DrawingContext *context, Rect areaToDraw, void *userData)
{
    BWOblateSphereoid *dealie = (BWOblateSphereoid *)userData;
    dealie->_frognatz = [NSColor plaidColor];
    // and more stuff.
} // drawEggThunk

...
@end // OblateSphereoid

Why?

So why can’t you just (somehow) get the address of -drawAnEggInRect: for the BWOblateSphereoid class and pass that for the drawing callback? The answer is a secret. Two of them, in fact.

When you use brackets in Objective-C to send a message to an object, the compiler is actually generating a call to the C function objc_msgSend (or to one of its variants, but let’s keep it simple):

id objc_msgSend(id self, SEL op, ...);

So something like [egg drawAnEggInRect: someRect] turns into:

objc_msgSend (egg, @selector(drawAnEggInRect:), someRect);

Which then calls drawAnEggInRect:, which under the covers boils down to something like this:

void drawAnEggInRect(id self, SEL selector, Rect someRect) ...

self and the selector are passed in as secret hidden parameters, and then any arguments for the method follow them. This is why you can’t just use the address for a method’s code in a C callback API. The library code invoking the callback will be wanting to put its own stuff in the first couple of arguments, which will cause all sorts of nifty explosions when Objective-C starts interpreting those as object pointers and selectors.

2 Comments »

  1. However, since drawEggThunk() is only being used within the file, it should be declared static for reasons of Hygiene.

    Comment by Ahruman — December 20, 2007 @ 7:34 pm

  2. Excellent point! Usually I’m better about making every function static unless it needs visibility . Good catch, sir.

    Comment by Mark Dalrymple — December 20, 2007 @ 8:44 pm


RSS feed for comments on this post.

Leave a comment

Blog at WordPress.com.