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.

18 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

  3. you’re wrong…. you can just use this [self methodForSelector:@selector(SomeCallbackMethod:)]; Maybe this is new since this posting… but I’m glad I figured it out. :)

    Comment by Jeff — February 13, 2010 @ 2:29 pm

  4. Hi Jeff,

    I’d love to see a project containing that – from my understanding of C and objective-C calling conventions, I can only see that as an avenue for disaster. SomeCallbackMethod: is expecting argument zero to be ‘self’ and argument one to be the selector, and C libraries typically don’t provide those.

    Comment by Mark Dalrymple — February 13, 2010 @ 6:53 pm

  5. no… you have to lookup methodForSelector… this is just a way to get the c equivalent function pointer for an objective c method. You just pass it the instance (in this case self), and the method (in this case SomeCallbackMethod). I have it working in my project and the objective c callback method is being called by the c method.

    Comment by Jeff — February 13, 2010 @ 8:33 pm

  6. Hi Jeff,

    I get a crash when I try that:

    (I’m sure the code formatting will get butchered :-( )

    ———–

    #import

    // gcc -g -Wall -framework Foundation -o sel sel.m

    @interface ObjectCallback : NSObject
    – (void)callback;
    @end

    @implementation ObjectCallback

    – (int) groovyness {
    return 23;
    }

    – (void)callback {
    NSLog(@”My spoon is too big %d”, [self groovyness]); // line 16
    }
    @end

    typedef void (*C_callback)(void);

    int main (void) {
    ObjectCallback *cb = [[ObjectCallback alloc] init];
    [cb callback];

    IMP method = [cb methodForSelector:@selector(callback)];

    // This is what the C library would be doing.
    C_callback c_function = (C_callback)method;
    c_function(); // line 30

    return (0);
    } // main

    ———-

    My output:

    % ./sel
    2010-02-13 19:51:02.814 sel[8987:903] My spoon is too big 23
    Segmentation fault

    and the stack trace:

    (gdb) where
    #0 0x00007fff8328110a in objc_msgSend ()
    #1 0x0000000100000dcb in -[ObjectCallback callback] (self=0x3, _cmd=0x7fff706b6\
    2c0) at sel.m:16
    #2 0x0000000100000e51 in main () at sel.m:30

    It *seems* to work if your -methodForSelector is trivial – makes no reference to self. If I don’t have the call to [self groovyness], it “works”, because the self and SEL parameters of the method invocation are not used. As soon as you call a method on the object, the value of “self” is bad, and so you crash.

    Once again, please show me the code that works in the general case.

    Thanks
    (always happy to be proved wrong, as my co-workers will attest with glee, but I don’t think I’m wrong on this one)

    Comment by Mark Dalrymple — February 13, 2010 @ 8:54 pm

  7. Readable version of the code is at http://borkware.com/hacks/sel.m

    Comment by Mark Dalrymple — February 13, 2010 @ 9:08 pm

  8. Hi Mark, I found this post for the exact reason you describe above. I am developing a cocoa application in Objective C using cocos2d and Chipmunk. As you may know, Chipmunk is based in C and uses callbacks and from the callback I need to call my object’s method to do some stuff.

    I am using the method you described above. The Objective C method is called correctly but the values received in the parameters are incorrect.

    What could be wrong. (I am using xCode 4.1)

    Comment by Andres — August 22, 2011 @ 2:11 pm

  9. “The Objective C method is called correctly but the values received in the parameters are incorrect.” – the point of this article is you can’t use objective-C methods directly, instead need to thunk off a C function. Post some code and I can take a look.

    Comment by Mark Dalrymple — August 22, 2011 @ 2:14 pm

  10. Mark, I am using Xcode 4.1 and cocos2d and chipmunk.

    I setup my callback like this:

    cpSpaceAddCollisionHandler(space, 1, 1, (cpCollisionBeginFunc)begin, NULL, NULL, NULL, self);

    and here are my callback functions:

    static void
    postStepAdd(cpSpace *space, cpShape *shape, void *data)
    {
    HelloWorldLayer *myLayer = (HelloWorldLayer *)data;

    [myLayer addNewSpriteX:300 y:200];

    }

    static int
    begin(cpArbiter *arb, cpSpace *space, void *data)
    {
    cpShape *a, *b; cpArbiterGetShapes(arb, &a, &b);

    cpSpaceAddPostStepCallback(space, (cpPostStepFunc)postStepAdd, b, data);

    return 1;
    }

    Here is my addNewSprite method:

    -(void) addNewSpriteX: (float)x y:(float)y
    {
    int posx, posy;

    NSLog(@”add New Sptrite at %f, %f”, x, y);

    CCSpriteBatchNode *batch = (CCSpriteBatchNode*) [self getChildByTag:kTagBatchNode];

    posx = (CCRANDOM_0_1() * 200);
    posy = (CCRANDOM_0_1() * 200);

    posx = (posx % 3) * 50;
    posy = (posy % 3) * 50;

    CCSprite *sprite = [CCSprite spriteWithBatchNode:batch rect:CGRectMake(posx, posy, 50, 50)];

    [batch addChild: sprite];

    sprite.position = ccp(x,y);

    posx = (CCRANDOM_0_1() * 800 – 400);
    posy = (CCRANDOM_0_1() * 800 – 400);

    cpBody *body = cpBodyNew(1.0f, cpMomentForCircle(1.0f, 0, 23, cpvzero));
    cpBodySetVel(body, ccp(posx, posy));

    // TIP:
    // since v0.7.1 you can assign CGPoint to chipmunk instead of cpVect.
    // cpVect == CGPoint
    body->p = ccp(x, y);
    cpSpaceAddBody(space, body);

    cpShape* shape = cpCircleShapeNew(body, 23, cpvzero);
    shape->e = 0.9f; shape->u = 0.1f;
    shape->data = sprite;
    shape->collision_type = 1;
    cpSpaceAddShape(space, shape);

    }

    It all works fine except that the x and y parameters get 0 and 0 instead of 300 and 200. So the sprites are correctly created but not at the place I want them.Note: the NSLog prints:

    add New Sprite at 0.000000, 0.000000

    I also tried calling addNewSprite like this.

    [myLayer addNewSpriteX:300.0f y:200.0f];

    Now NSLog prints:

    add New Srite at 0.000000, 3.792969

    What could I be doing wrong?

    Andres

    Comment by Andres — August 22, 2011 @ 2:49 pm

  11. At a glance, that should work. Has the compiler seen the declaration of addNewSpriteX:y: before it’s called (it’s in the header, or addNewSprite is declared above the C function). The compiler might be passing the wrong types (ints if you used 300, doubles if you used 300.f, when the method is expecting floats).

    Things I’d try:
    – make sure there’s no compiler warnings
    – ensure the addNewSpriteX is declared before its first use
    – change addNewSpriteX to take doubles instead of floats and see if it works.

    Comment by Mark Dalrymple — August 22, 2011 @ 3:04 pm

  12. Thanks Mark, your pointers helped me find the solution.

    I had to move the C callback functions inside the Class definition, above my init method so that they are defined before I try to initialize the callback and below the addNewSprite method so that it knows what types to pass.

    Is this correct?

    Is there a more elegant way to do it?

    Comment by Andres — August 22, 2011 @ 4:56 pm

  13. You can use a category or a class extension at the top of the file to forward-delcare methods .

    @interface GroovyClass ()
    – (void) addNewSpriteX: (float) x y: (float) y;
    @end // Extension

    // C functions can go here, or wherever, so long as after the above declaration

    @implementation GroovyClass
    // … wherever makes sense
    – (void) addNewSpriteX: (float) x y: (float) y {

    Comment by Mark Dalrymple — August 22, 2011 @ 5:13 pm

  14. Awesome, I never thought I would get support so fast. Thanks for the quick response and great help.

    Andres

    Comment by Andres — August 22, 2011 @ 5:24 pm

  15. Glad to be of service :-D

    Comment by Mark Dalrymple — August 22, 2011 @ 6:57 pm

  16. Guys, my article on c++ to objective-c callbacks might help you to understand how to do it the easier way: http://alex.tapmania.org/2012/04/c-to-obj-c-callbacks-part-3.html
    HTH,
    Alex

    Comment by lerosash — April 8, 2012 @ 11:20 am

  17. Makes me appreciate straight Objective-C that much more . I couldn’t make heads nor tails of that.

    Comment by Mark Dalrymple — April 8, 2012 @ 12:15 pm

  18. Hello,

    I just read your post and find it awesome trying to understand the differences C and Objective C for structures and callback.
    Many thanks
    Jean Marie

    Comment by Jean Marie — August 10, 2014 @ 10:50 am


RSS feed for comments on this post.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: