Is there a way to wrap an ObjectiveC block into function pointer?

前端 未结 4 1819
忘了有多久
忘了有多久 2020-12-10 05:51

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 i

相关标签:
4条回答
  • 2020-12-10 06:07

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

    0 讨论(0)
  • 2020-12-10 06:12

    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.

    0 讨论(0)
  • 2020-12-10 06:17

    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.

    0 讨论(0)
  • 2020-12-10 06:23

    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)

    0 讨论(0)
提交回复
热议问题