Function Pointers in Objective C

2020-02-03 04:54发布

问题:

Quick question. Does anyone know how to get the function pointer of an objective c method? I can declare a C++ method as a function pointer, but this is a callback method so that C++ method would need to be part of the class SO THAT IT CAN ACCESS THE INSTANCE FIELDS. I don't know how to make a C++ method part of an objective c class. Any suggestions?

回答1:

Typically, you need two pieces of information to call back into Objective-C; the method to be invoked and the object to invoke it upon. Neither just a selector or just the IMP -- the instanceMethodForSelector: result -- will be enough information.

Most callback APIs provide a context pointer that is treated as an opaque value that is passed through to the callback. This is the key to your conundrum.

I.e. if you have a callback function that is declared as:

typedef void (*CallBackFuncType)(int something, char *else, void *context);

And some API that consumes a pointer of said callback function type:

void APIThatWillCallBack(int f1, int f2, CallBackFuncType callback, void *context);

Then you would implement your callback something like this:

void MyCallbackDude(int a, char *b, void *context) {
    [((MyCallbackObjectClass*)context) myMethodThatTakesSomething: a else: b];
}

And then you would call the API something akin to this:

MyCallbackObjectClass *callbackContext = [MyCallbackObjectClass new];
APIThatWillCallBack(17, 42, MyCallbackDude, (void*)callbackContext);

If you need to switch between different selectors, I would recommend creating a little glue class that sits between the callback and the Objective-C API. The instance of the glue class could contain the configuration necessary or logic necessary to switch between selectors based on the incoming callback data.



回答2:

You can do it with @selector and the SEL type:

SEL sel = @selector(myMethod:);

/* Equivalent to [someObject myMethod:Paramater] */
[someObject performSelector:sel withObject:Parameter]

There is also the IMP type...

IMP imp = [someObject methodForSelector:sel];

/* don't remember if this syntax is correct; it's been a while...
 * the idea is that it's like a function pointer. */
imp(someObject, sel, Parameter); 

Update based on your comments

If you don't want to have to specify the object, in this case you are asking for something awfully non-portable. Essentially you want lambda expressions which is not a feature of C, though it's coming in C++0x.

So my solution is going to be "out there", sketchy, and non portable...

BUT... maybe you can do it with runtime code generation...

You can start by writing a stub function in assembly (assuming you want x86)

push dword 0 ; SEL will go here
push dword 0 ; Object will go here
push dword 0 ; IMP will go here
pop eax      ; eax = imp
call eax     ; call imp
add esp, 8   ; cleanup stack
ret          ; return

This assembles to:

0068 0000 6800 0000 0000 0068 0000 5800 d0ff c481 0004 0000 00c3

Note the instruction push dword 0 is the bytes 68 00 00 00 00. We will fill in the zeros to a pointer at runtime.

So, we can copy that into a malloc()'d buffer, patch it up, and call mprotect() to make it executable.

Below code is for illustrative purposes and I have no idea if it works. :-)

/* x86 code... */
char code[] = { 0x68, 0x00, 0x00, 0x00, 0x00, 0x68,
                0x00, 0x00, 0x00, 0x00, 0x68, 0x00,
                0x00, 0x00, 0x00, 0x58, 0xff, 0xd0,
                0x81, 0xc4, 0x04, 0x00, 0x00, 0x00,
                0xc3 };

char *buf = malloc(sizeof(code));

SEL Selector = @selector(Method);
IMP Imp = [object methodForSelector:Selector];

/* Copy template */
memcpy(buf, code, sizeof(code));

/* Patch the "push dword 0" parts with your arguments
 * This assumes everything is 32-bit, including SEL, IMP, etc. */
memcpy(buf + 1, &Selector, sizeof(Selector));
memcpy(buf + 6, &object, sizeof(object));
memcpy(buf + 11, &Imp, sizeof(Imp));

/* Now here comes the sketchy part...
 * Make it executable and turn it into a function pointer.  */
mprotect(buf, sizeof(code), PROT_EXEC);
void (*Function)() = (void(*)())buf;

/* Now, crazy as it sounds, you should be able to do: */
Function();

You might want to do an [object retain] for as long as this function exists, and [object release] when and if you should choose to free it. (Probably best to wrap this sketcyness inside an object anyway, then use normal objc refcounting to control the buffer and a reference to object.) Maybe you'll also want to use mmap() to allocate instead of malloc()...

If that sounds needlessly complex that's because it is. :-)



回答3:

You can create a C++ class as a wrapper. And call the objective C message from that class. Just remember to make your file yoursource.mm instead of yoursource.cpp.



回答4:

You can do this with instanceMethodForSelector: See the Apple NSObject documentation for more info.