我必须为iOS应用中的特定C库提供C风格的回调。回调没有void *userData
或类似的东西。所以我无法在上下文中循环。我想避免引入全局背景来解决这个问题。一个理想的解决方案是Objective-C块。
我的问题:有没有办法将一个块“强制转换”为一个函数指针或以某种方式包装/隐藏它?
答案 0 :(得分:7)
从技术上讲,您可以访问该块的函数指针。但这样做完全不安全,所以我当然不推荐它。要了解具体方法,请考虑以下示例:
#import <Foundation/Foundation.h>
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
};
int main(int argc, char *argv[]) {
@autoreleasepool {
// Block that doesn't take or return anything
void(^block)() = ^{
NSLog(@"Howdy %i", argc);
};
// Cast to a struct with the same memory layout
struct Block_layout *blockStr = (struct Block_layout *)(__bridge void *)block;
// Now do same as `block()':
blockStr->invoke(blockStr);
// Block that takes an int and returns an int
int(^returnBlock)(int) = ^int(int a){
return a;
};
// Cast to a struct with the same memory layout
struct Block_layout *blockStr2 = (struct Block_layout *)(__bridge void *)returnBlock;
// Now do same as `returnBlock(argc)':
int ret = ((int(*)(void*, int a, ...))(blockStr2->invoke))(blockStr2, argc);
NSLog(@"ret = %i", ret);
}
}
运行产生:
Howdy 1
ret = 1
我们期望直接用block()
纯粹执行这些块。因此,您可以使用invoke
作为函数指针。
但正如我所说,这完全不安全。实际上不要使用它!
如果你想看到一种方法来做你要求的事情,那么检查一下: http://www.mikeash.com/pyblog/friday-qa-2010-02-12-trampolining-blocks-with-mutable-code.html
这只是对你需要做些什么才能让它发挥作用的一个很好的记录。遗憾的是,它永远不会在iOS上工作(因为你需要将页面标记为可执行文件,而你的应用程序的沙箱中不允许这样做)。但是,这是一篇很棒的文章。
答案 1 :(得分:4)
如果您的块需要上下文信息,并且回调不提供任何上下文,我恐怕答案是明确的。块必须在某处存储上下文信息,因此您永远无法将这样的块转换为无参数函数指针。
在这种情况下,精心设计的全局变量方法可能是最佳解决方案。
答案 2 :(得分:1)
MABlockClosure可以做到这一点。但是,无论你需要什么,它都可能有点过分。
答案 3 :(得分:1)
我知道这已经解决了,但是对于有兴趣的人士,我有另一个解决方案。
将整个函数重新映射到新的地址空间。新生成的地址可用作所需数据的密钥。
#import <mach/mach_init.h>
#import <mach/vm_map.h>
void *remap_address(void* address, int page_count)
{
vm_address_t source_address = (vm_address_t) address;
vm_address_t source_page = source_address & ~PAGE_MASK;
vm_address_t destination_page = 0;
vm_prot_t cur_prot;
vm_prot_t max_prot;
kern_return_t status = vm_remap(mach_task_self(),
&destination_page,
PAGE_SIZE*(page_count ? page_count : 4),
0,
VM_FLAGS_ANYWHERE,
mach_task_self(),
source_page,
FALSE,
&cur_prot,
&max_prot,
VM_INHERIT_NONE);
if (status != KERN_SUCCESS)
{
return NULL;
}
vm_address_t destination_address = destination_page | (source_address & PAGE_MASK);
return (void*) destination_address;
}
请记住处理不再需要的页面,并注意每次调用所需的内存比MABlockClosure多。
(在iOS上测试)