Is there a way to wrap an ObjectiveC block into fu

2020-02-05 09:22发布

I have to provide a C-style callback for a specific C library in an iOS app. The callback has no void *userData or something similar. So I am not able to loop in a context. I'd like to avoid introducing a global context to solve this. An ideal solution would be an Objective-C block.

My question: Is there a way to 'cast' a block into a function pointer or to wrap/cloak it somehow?

4条回答
甜甜的少女心
2楼-- · 2020-02-05 09:29

If your block needs context information, and the callback does not offer any context, I'm afraid the answer is a clear no. Blocks have to store context information somewhere, so you will never be able to cast such a block into a no-arguments function pointer.

A carefully designed global variable approach is probably the best solution in this case.

查看更多
Animai°情兽
3楼-- · 2020-02-05 09:32

I know this has been solved but, for interested parties, I have another solution.

Remap the entire function to a new address space. The new resulting address can be used as a key to the required data.

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

Remember to handle pages that aren't required anymore and note that it takes a lot more memory per invocation than MABlockClosure.

(Tested on iOS)

查看更多
你好瞎i
4楼-- · 2020-02-05 09:51

Technically, you could get access to a function pointer for the block. But it's totally unsafe to do so, so I certainly don't recommend it. To see how, consider the following example:

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

Running that yields:

Howdy 1
ret = 1

Which is what we'd expect from purely executing those blocks directly with block(). So, you could use invoke as your function pointer.

But as I say, this is totally unsafe. Don't actually use this!

If you want to see a write-up of a way to do what you're asking, then check this out: http://www.mikeash.com/pyblog/friday-qa-2010-02-12-trampolining-blocks-with-mutable-code.html

It's just a great write-up of what you would need to do to get this to work. Sadly, it's never going to work on iOS though (since you need to mark a page as executable which you're not allowed to do within your app's sandbox). But nevertheless, a great article.

查看更多
来,给爷笑一个
5楼-- · 2020-02-05 09:52

MABlockClosure can do exactly this. But it may be overkill for whatever you need.

查看更多
登录 后发表回答