从c++到目标c的回调

callback from c++ to objective c

本文关键字:回调 目标 c++      更新时间:2023-10-16

我在objective-c中有ViewController,我的大部分代码是c++(.mm)。我想从obj-c(在c++中)设置一些对成员函数的回调,并从c++调用它们。类似这样的东西(非常简单):

@interface MyClass
{ }
-(void)my_callback;
@end
@implementation MyClass
-(void)my_callback
{
   printf("called!n");
}
-(void)viewDidLoad
{
   // setup_callback( "to my_callback ?" );
}
@end

和:

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

这当然是不对的。有什么建议我该怎么做吗?

您有几个选项。

使用块

您可以使用块来传达您的回调工作。这可能是最简单的解决方案,因为它允许您在不必向回调"函数"传递任何参数的情况下调用代码。块在C及其所有带有Clang的超集中工作,Clang++甚至允许在块和lambda之间进行隐式类型转换。

#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];
    });
}

这可能需要对C++端进行一些重新设计,以接受函子(或者如果更简单的话,只接受块)而不是函数指针。

由于块可以创建闭包,所以它们对于这类工作非常方便。

Blocks是Apple对C、C++和Objective-C的扩展。点击此处查看更多关于他们的信息。

使用Objective-C运行时获取指向要调用的方法的函数指针

使用Objective-C运行时访问选择器的函数指针。这更为繁琐,需要跟踪三个变量(要调用方法的对象、要使用的选择器和方法实现),但即使在不能使用Objective-C语法的情况下,它也能正常工作。

Objective-C方法实现是具有以下签名的函数指针:

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

其中self是您所期望的,_cmd是导致此方法调用的选择器(_cmd变量实际上在所有Objective-C方法中都可用,请尝试),其余变量则被视为可变变量。您需要将IMP变量强制转换为正确的函数签名,因为变量C函数的调用约定并不总是与Objective-C方法调用的调用约定匹配(Objective-C方法调用是编译器的标准函数调用约定,可能是cdecl或amd64调用约定,而variadic调用约定并不总是相同)。reinterpret_cast将能够做到这一点。

以下是我为类似目的编写的一些代码。它使用C++11可变模板来帮助获得正确的函数签名。

#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));
}

在使用函数调用之前,还要注意您的实例不是nil,因为nil检查是由Objective-C运行时处理的。在这种情况下,我们绕过它。

跟踪对象和SEL

使用-[NSObject performSelector:]执行回调。基本上是Objective-C运行时解决方案的一个更简单的版本。

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));
}

将调用封装在C++函数中

我认为这个并不需要任何例子。创建一个接受对象类型作为第一个参数的函数,并调用所需的方法。与SEL解决方案类似,您需要分别跟踪要调用的函数和要调用它的对象。

因为C++和Obj-C都是C的超集,所以可以将C++代码回调到C函数。要将回调传递回您的Obj-C对象,只需确保C回调参数包含一个指向原始Obj-C对象的指针。

// C++ calls this C function
void exampleCallback(void* classInstance)
{
    // callback arguments include a pointer to the Obj-C object
    [((MYObjCClassType*)classInstance) exampleCallback];
}

我认为这是不可能的:在C++代码中调用Objective-C回调。反过来也是可能的。

当我遇到这个问题时,我所做的是编写一个在Objective-C中永远运行的EVENT_LOOP,并从C++std::vector实例中获取事件。这样,在C++中,如果您想在Objective-C中"调用"回调,只需将事件添加到相应的std::vector中。

您可以在.mm文件中自由混合Obj-C和C++,这被称为"Objective-C++"。

为了保持C++代码和Objective-C代码的隔离,您可以在它们之间创建一个轻量级的facade,从C++回调中(以通常的方式)调用Objective-C方法。