从C++回调到Objective C

13

我有一个Objective-C的ViewController,大部分代码是C++ (.mm)。 我想设置一些从C++回调到Objective-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(); }

这当然是不正确的。请问有什么建议可以帮助我实现它吗?

4个回答

18

你有几个选择。

使用块

你可以使用块来传达回调的工作。这可能是最简单的解决方案,因为它允许你调用代码而无需将任何参数传递给回调“函数”。块在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++ 端进行一些修改,以接受函数对象(或者只是块,如果更简单的话)而不是函数指针。

由于块创建了闭包,它们非常适合这种工作。

块是 Apple 对 C、C++ 和 Objective-C 的扩展。在此处了解更多信息。

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

使用 Objective-C 运行时来访问您选择器的函数指针。这更加繁琐,需要您跟踪三个变量(要调用方法的对象、要使用的选择器和方法实现),但它在您无法使用 Objective-C 语法的情况下仍然有效。

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

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

这里的self是您所期望的内容,_cmd是导致该方法调用的选择器(在所有Objective-C方法中实际上都可以使用_cmd变量,请尝试),其余部分被视为可变参数。由于可变C函数的调用约定并不总是与Objective-C方法调用的调用约定相匹配(Objective-C方法调用是编译器的标准函数调用约定,可能是cdecl或amd64调用约定,而可变调用约定并不总是相同的),因此您需要IMP变量强制转换为适当的函数签名。 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解决方案类似,您需要单独跟踪要调用的函数和要调用它的对象。


2
你甚至不需要获取实现,就可以使用objc_msgSend来代替实现。 - user102008
如果是True,那么你将携带选择器而不是方法指针。 - zneak
不需要。使用您的IMP,您仍然必须携带选择器。因此,它是相同的,只是现在您不需要获取IMP。 - user102008
使用代码块。很棒。谢谢! - jrc

2

因为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];
}

1
要执行您所展示的强制转换,您需要使用无需付费的桥接:[((__bridge MYObjCClassType*)classInstance) exampleCallback]; - dadude999

1

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

当我遇到这个问题时,我写了一个永远运行的EVENT_LOOP在Objective-C中,并从C++的std::vector实例中获取事件。这样,在C++中,如果你想要“调用”Objective-C中的回调函数,你只需要将事件添加到相应的std::vector中。


1

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接