callback from c++ to objective c

2019-03-11 09:44发布

I have ViewController in objective-c and most of my code is c++ (.mm). I'd like to setup some callbacks to member functions from obj-c (in c++) and call them from c++. Something like this (it's very simplifyed):

@interface MyClass
{ }
-(void)my_callback;
@end

@implementation MyClass

-(void)my_callback
{
   printf("called!\n");
}

-(void)viewDidLoad
{
   // setup_callback( "to my_callback ?" );
}
@end

and:

void setup_callback(void(*func)()) { func(); }

this is not correct of course. Any advice how can I do it, please?

4条回答
Juvenile、少年°
2楼-- · 2019-03-11 10:18

Because C++ and Obj-C are both supersets of C, you can have your C++ code callback to a C function. To communicate the callback back to your Obj-C object, just make sure that the C callback arguments includes a pointer to the original Obj-C object.

// C++ calls this C function
void exampleCallback(void* classInstance)
{
    // callback arguments include a pointer to the Obj-C object
    [((MYObjCClassType*)classInstance) exampleCallback];
}
查看更多
smile是对你的礼貌
3楼-- · 2019-03-11 10:21

You can freely mix Obj-C and C++ in your .mm files - it's referred to as "Objective-C++".

To keep isolation of your C++ code and Objective-C code, you could create a lightweight facade between them which would make Objective-C method calls (in the usual way) from within C++ callbacks.

查看更多
Rolldiameter
4楼-- · 2019-03-11 10:23

I don't think that's possible: calling an Objective-C callback within C++ code. The other way around is possible.

What I did when I had this issue is to write an EVENT_LOOP running forever in Objective-C, and get the events from a C++ std::vector instance. That way, in C++, if you want to "call" a callback in Objective-C, you just add the event to the corresponding std::vector.

查看更多
Explosion°爆炸
5楼-- · 2019-03-11 10:28

You have a few options.

Using blocks

You may use blocks to convey your callback work. This is probably the simplest solution as it allows you to call your code without having to pass any parameter to the callback "function". Blocks work in C and all its supersets with Clang, and Clang++ even allows implicit casts between blocks and lambdas.

#include <dispatch/dispatch.h>

void setup_callback(dispatch_block_t block)
{
    // required to copy the block to the heap, otherwise it's on the stack
    dispatch_block_t copy = [block copy];

    // setup stuff here
    // when you want to call the callback, do as if it was a function pointer:
    // block();
}

int main()
{
    MyClass* instance = [[MyClass alloc] init];

    setup_callback(^{
        [instance callback_method];
    });
}

That might require some reworking on the C++ end to accept functors (or just blocks if it's simpler) instead of function pointers.

Since blocks create closures, they're very convenient for that kind of works.

Blocks are an Apple extension to C, C++ and Objective-C. See more about them here.

Use the Objective-C runtime to acquire the function pointer to the method you want to call

Use the Objective-C runtime to access the function pointer of your selector. This is more tedious and requires you to keep track of three variables (the object to call the method on, the selector to use, and the method implementation), but it actually works even in the case you can't use the Objective-C syntax.

Objective-C method implementations are function pointers with this signature:

typedef void (*IMP)(id self, SEL _cmd, ...);

Where self is what you'd expect, _cmd is the selector that caused this method call (the _cmd variable is actually available in all Objective-C methods, try it), and the rest is considered variadic. You need to cast IMP variables into the proper function signature because the calling convention for variadic C functions doesn't always match the calling convention for Objective-C method calls (the Objective-C method call is the standard function calling convention for your compiler, probably either cdecl or the amd64 calling convention, and the variadic calling convention is not always the same). A reinterpret_cast will be able to do it.

Here's some code I put together for similar intents. It uses C++11 variadic templates to help with getting the proper function signature.

#include <objc/runtime.h>

template<typename TReturnType, typename... TArguments>
auto GetInstanceMethodPointer(Class class, SEL selector) -> TReturnType (*)(id, SEL, TArguments...)
{
    Method m = class_getInstanceMethod(class, selector);
    IMP imp = method_getImplementation(m);
    return reinterpret_cast<TReturnType (*)(id, SEL, TArguments...)>(imp);
}

int main()
{
    MyClass* instance = [[MyClass alloc] init];
    auto foo = GetInstanceMethodPointer<void>(
        [MyClass class],
        @selector(my_callback));
    // foo is a void (*)(id, SEL) function pointer
    foo(instance, @selector(my_callback));
}

Also take care that your instance is not nil before using the function call, because nil checking is handled by the Objective-C runtime. In this case, we're bypassing it.

Keep track of an object and a SEL

Use -[NSObject performSelector:] to perform your callback. Basically a simpler version of the Objective-C runtime solution.

void setup_callback(id object, SEL selector)
{
    // do stuff
    // to execute the callback:
    // [object performSelector:selector];
}

int main()
{
    MyClass* instance = [[MyClass alloc] init];

    setup_callback(instance, @selector(my_callback));
}

Wrapping your call inside a C++ function

I think this one doesn't really need any example. Create a function that accepts your object type as the first parameter and call the method you want on it. Similarly to the SEL solution, you then need to separately keep track of the function to call and the object on which to call it.

查看更多
登录 后发表回答