10

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 Answers 4

7

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.

Sign up to request clarification or add additional context in comments.

4 Comments

Please excuse my ignorance, but why exactly is it unsafe? I suppose it is because the struct is an internal one and could change in the future, right?
The problem with this is that it still requires you to pass the block to the invoke function, whereas the OP says you cannot pass a context to the callback.
Yeh indeed... I was trying to come up with a mad way but haven't found a way yet. Of course, it's likely there isn't a way :-).
This isn't totally unsafe. The Block's layout is fixed at compile time and "baked in" to your executable. It won't break in the field. It may only require modification the next time you compile.
4

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.

Comments

2

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

Comments

1

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)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.