目标 C 中的函数指针

Function Pointers in Objective C

提问人:Sam Stewart 提问时间:11/22/2009 最后编辑:Sam Stewart 更新时间:11/22/2009 访问量:12588

问:

快速提问。有谁知道如何获取目标 c 方法的函数指针? 我可以将 C++ 方法声明为函数指针,但这是一个回调方法,因此 C++ 方法需要成为类的一部分,以便它可以访问实例字段。我不知道如何使 C++ 方法成为目标 C 类的一部分。 有什么建议吗?

C++ Objective-C 函数 指针

评论


答:

1赞 Pablo Santa Cruz 11/22/2009 #1

您可以创建 C++ 类作为包装器。并从该类调用目标 C 消息。只要记住让你的文件 yoursource.mm 而不是你的源.cpp

13赞 asveikau 11/22/2009 #2

你可以用和类型来做到这一点:@selectorSEL

SEL sel = @selector(myMethod:);

/* Equivalent to [someObject myMethod:Paramater] */
[someObject performSelector:sel withObject:Parameter]

还有类型...IMP

IMP imp = [someObject methodForSelector:sel];

/* don't remember if this syntax is correct; it's been a while...
 * the idea is that it's like a function pointer. */
imp(someObject, sel, Parameter); 

根据您的意见进行更新

如果你不想指定对象,在这种情况下,你要求的东西非常不可移植。从本质上讲,你想要的 lambda 表达式不是 C 的功能,尽管它是在 C++0x 中出现的。

所以我的解决方案将是“外面的”,粗略的,非便携式的......

但。。。也许你可以用运行时代码生成来做到这一点......

你可以从在程序集中编写一个存根函数开始(假设你想要 x86)

push dword 0 ; SEL will go here
push dword 0 ; Object will go here
push dword 0 ; IMP will go here
pop eax      ; eax = imp
call eax     ; call imp
add esp, 8   ; cleanup stack
ret          ; return

这将组装成:

0068 0000 6800 0000 0000 0068 0000 5800 d0ff c481 0004 0000 00c3

请注意,该指令是字节。我们将在运行时将零填充到指针。push dword 068 00 00 00 00

因此,我们可以将其复制到“d缓冲区”中,对其进行修补,然后调用以使其可执行。malloc()mprotect()

下面的代码用于说明目的,我不知道它是否有效。:-)

/* x86 code... */
char code[] = { 0x68, 0x00, 0x00, 0x00, 0x00, 0x68,
                0x00, 0x00, 0x00, 0x00, 0x68, 0x00,
                0x00, 0x00, 0x00, 0x58, 0xff, 0xd0,
                0x81, 0xc4, 0x04, 0x00, 0x00, 0x00,
                0xc3 };

char *buf = malloc(sizeof(code));

SEL Selector = @selector(Method);
IMP Imp = [object methodForSelector:Selector];

/* Copy template */
memcpy(buf, code, sizeof(code));

/* Patch the "push dword 0" parts with your arguments
 * This assumes everything is 32-bit, including SEL, IMP, etc. */
memcpy(buf + 1, &Selector, sizeof(Selector));
memcpy(buf + 6, &object, sizeof(object));
memcpy(buf + 11, &Imp, sizeof(Imp));

/* Now here comes the sketchy part...
 * Make it executable and turn it into a function pointer.  */
mprotect(buf, sizeof(code), PROT_EXEC);
void (*Function)() = (void(*)())buf;

/* Now, crazy as it sounds, you should be able to do: */
Function();

只要此函数存在,您可能希望执行此函数,以及何时以及是否应该选择释放它。(无论如何,最好将这种 sketcyness 包装在一个对象中,然后使用普通的 objc refcounting 来控制缓冲区和对 .也许你还想用来分配而不是......[object retain][object release]objectmmap()malloc()

如果这听起来不必要地复杂,那是因为它确实如此。:-)

评论

0赞 Mark Bessey 11/22/2009
第二个代码片段确实提供了 C 样式的函数指针,如请求的那样。不过,细节可能有点棘手。
0赞 Sam Stewart 11/22/2009
这是一个不错的选择,但我只想传递一个指向该函数的指针。也不是指向对象的指针。否则,我只会使用选择器。
1赞 bbum 11/22/2009
通常,IMP 和选择器都不会为您提供太多帮助,因为您还需要对需要回调的实例进行引用。
0赞 bbum 11/22/2009
或者你可以使用 libffi 以可移植的方式创建一个闭包。但你会遇到一些与安全相关的令人讨厌的问题(代码必须位于标记为可执行的页面上,并非所有操作系统都支持)。
1赞 Sam Stewart 11/24/2009
另外,为了大家的利益,这个问题主要是我自己的无知问题。我有一些想法,我可以在 Obj-C 中制作一个指向 Object 方法的函数指针。嗯,这当然是错误的。Obj-C 方法没有绝对地址。Obj-c 方法地址是在运行时确定的。
1赞 Mark Bessey 11/22/2009 #3

有关详细信息,请参阅 Apple NSObject 文档instanceMethodForSelector:

15赞 bbum 11/22/2009 #4

通常,您需要两条信息才能回调到 Objective-C 中;要调用的方法和要调用它的对象。无论是选择器还是IMP - 结果 - 都不足以提供足够的信息。instanceMethodForSelector:

大多数回调 API 都提供上下文指针,该指针被视为传递到回调的不透明值。这是你难题的关键。

即,如果你有一个声明为以下的回调函数:

typedef void (*CallBackFuncType)(int something, char *else, void *context);

以及一些使用所述回调函数类型的指针的 API:

void APIThatWillCallBack(int f1, int f2, CallBackFuncType callback, void *context);

然后,您将实现如下回调:

void MyCallbackDude(int a, char *b, void *context) {
    [((MyCallbackObjectClass*)context) myMethodThatTakesSomething: a else: b];
}

然后,您将 API 称为类似于以下内容:

MyCallbackObjectClass *callbackContext = [MyCallbackObjectClass new];
APIThatWillCallBack(17, 42, MyCallbackDude, (void*)callbackContext);

如果你需要在不同的选择器之间切换,我建议创建一个介于回调和 Objective-C API 之间的小胶水类。glue 类的实例可以包含根据传入的回调数据在选择器之间切换所需的配置或逻辑。

评论

0赞 Sam Stewart 11/22/2009
嗯......好提示。我想这与使用目标/动作是一回事。基本上,你必须告诉那个愚蠢的回拨函数,只是要和谁说话。如果我使用的是我没有源代码的 API,它会很有用。实际上,我只是用我自己的代码实现整个设置。不过,非常感谢。
0赞 bbum 11/22/2009
它类似于目标/行动,但并不完全相同。如果编写纯 C 语言,则上下文将是包含回调中所需的任何状态的内存块。这几乎就是这里发生的事情,但您只是将回调转发到 Objective-C 中......
0赞 outis 11/22/2009
使用 Cocoa 进行开发时,NSInvocation 可以是胶水类:developer.apple.com/mac/library/documentation/cocoa/reference/...
0赞 bbum 11/22/2009
您可以使用 NSInvocation,但它很快就会变得混乱,并且几乎总是会破坏编译器执行类型检查的大部分功能。胶水类编写起来很简单,以后可以很容易地扩展,并最大限度地提高编译器仔细检查代码的能力。