Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can a C callback return an Objective-C object through a userdata pointer?

I am using a third-party library that requires a C callback in an iOS program. The callback provides a void* userdata parameter that I can use as I wish. In this callback I want to create an NSData object that is returned to the code that invokes the API that eventually calls the callback. For example:

// C callback:
static void callback(int x, void* userdata)
{
    if (userdata)
    {
        // This gives an error:
        // Pointer to non-const type 'NSData*' with no explicit ownership
        NSData** p = (NSData**)userdata;

        *p = [NSData init:...];
    }
}

// main code:
NSData* d = ...;  // sometimes valid, sometimes nil
library_api(callback, &d);  // have the callback initialize or replace d
if (d)
{
    [d doSomthing:...];
}

I am using ARC, which I think is at the root of the issues. I thought of using a struct to hold the NSData*, but ARC forbids Objective-C objects in structs or unions. There is probably some variant of __bridge that might help, but I am not sure which. I have looked at a lot of SO questions and read the Apple docs, but it is not clear to me how to code this up.

Ideally, if d were not nil at the time the callback is invoked, its current value would be released and a new NSData object would replace it. In other words, when library_api completes, d always holds the NSData object created by the callback, and any previously held value would have been released properly.

I suppose I could keep the Objective-C code out of the callback and just malloc() a buffer to store the data which would then be copied to an NSData object in the main code, but I am hoping that I can avoid that extra step.

like image 694
Randall Cook Avatar asked Sep 16 '13 21:09

Randall Cook


2 Answers

You can't transfer object ownership to a void*, but you can transfer object ownership to a CoreFoundation type, like CFDataRef or CFTypeRef. You're then free to pass around the CF pointer as you wish.

e.g.:

NSData* data = ...

CFTypeRef cfData = (__bridge CFTypeRef)data; // If you want to pass in data as well.

library_api(callback, &cfData);

if (cfData)
{
    data = (__bridge_transfer NSData*)cfData; // Transfer ownership to ARC.  No need to call CFRelease(cfData).
    NSLog@("Data: %@", data);
}

And in the callback:

static void callback(int x, void* userdata)
{
    if (userdata)
    {
        CFTypeRef* cfDataPtr = userdata;

        CFTypeRef cfInData = *cfDataPtr;
        NSData* inData = (__bridge NSData*)cfInData;
        NSLog(@"In Data: %@", inData);

        NSData* outData = [[NSData alloc] init];
        CFTypeRef cfOutData = (__bridge_retained CFTypeRef)outData; // Transfer ownership out of ARC.
        *cfDataPtr = cfOutData;
    }
}
like image 68
Darren Avatar answered Nov 18 '22 04:11

Darren


You can have struct contain ObjC object, they just have to be __unretained_unsafe OR in .mm file.

There are few options:

1 Use a struct to hold ObjC object in .m file

struct MyStruct
{
    __unretained_unsafe id obj;
}

2 Use a struct to hold ObjC object in .mm file (Objective-C++) and use C++ new/delete for the struct. You can do this because compiler will generate retain/release in the constructor/destructor of the struct.

struct MyStruct
{
    id obj; // no need to have __unretained_unsafe
}

3 Change the signature of the callback function and cast it (I didn't try this before)

4 Just make this file non ARC with -fno-objc-arc compiler flag

like image 29
Bryan Chen Avatar answered Nov 18 '22 05:11

Bryan Chen