9

我必须为 iOS 应用程序中的特定 C 库提供 C 风格的回调。回调没有void *userData或类似的东西。所以我无法在上下文中循环。我想避免引入全局上下文来解决这个问题。一个理想的解决方案是 Objective-C 块。

我的问题:有没有办法将块“转换”到函数指针或以某种方式包装/隐藏它?

4

4 回答 4

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 上运行(因为您需要将页面标记为可执行,而您不允许在应用程序的沙箱中执行此操作)。但无论如何,一篇很棒的文章。

于 2012-10-22T11:34:13.367 回答
4

如果您的块需要上下文信息,而回调不提供任何上下文,恐怕答案是明确的不。块必须在某处存储上下文信息,因此您将永远无法将这样的块转换为无参数函数指针。

在这种情况下,精心设计的全局变量方法可能是最好的解决方案。

于 2012-10-22T07:31:09.607 回答
2

MABlockClosure可以做到这一点。但这对于您需要的任何东西都可能是矫枉过正的。

于 2014-07-14T02:16:24.130 回答
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 上测试)

于 2017-11-19T16:06:06.727 回答