Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

using objc_msgSend to call a Objective C function with named arguments

I want to add scripting support for an Objective-C project using the objc runtime. Now I face the problem, that I don't have a clue, how I should call an Objective-C method which takes several named arguments.

So for example the following objective-c call

[object foo:bar]; 

could be called from C with:

objc_msgSend(object, sel_getUid("foo:"), bar); 

But how would I do something similar for the method call:

[object foo:var bar:var2 err:errVar]; 

??

Best Markus

like image 336
Markus Pilman Avatar asked Apr 04 '10 07:04

Markus Pilman


2 Answers

The accepted answer is close, but it won't work properly for certain types. For example, if the method is declared to take a float as its second argument, this won't work.

To properly use objc_msgSend, you have to cast it to the the appropriate type. For example, if your method is declared as

- (void)foo:(id)foo bar:(float)bar err:(NSError **)err 

then you would need to do something like this:

void (*objc_msgSendTyped)(id self, SEL _cmd, id foo, float bar, NSError**error) = (void*)objc_msgSend; objc_msgSendTyped(self, @selector(foo:bar:err:), foo, bar, error); 

Try the above case with just objc_msgSend, and log out the received arguments. You won't see the correct values in the called function. This unusual casting situation arises because objc_msgSend is not intended to be called like a normal C function. It is (and must be) implemented in assembly, and just jumps to a target C function after fiddling with a few registers. In particular, there is no consistent way to refer to any argument past the first two from within objc_msgSend.

Another case where just calling objc_msgSend straight wouldn't work is a method that returns an NSRect, say, because objc_msgSend is not used in that case, objc_msgSend_stret is. In the underlying C function for a method that returns an NSRect, the first argument is actually a pointer to an out value NSRect, and the function itself actually returns void. You must match this convention when calling because it's what the called method will assume. Further, the circumstances in which objc_msgSend_stret is used differ between architectures. There is also an objc_msgSend_fpret, which should be used for methods that return certain floating point types on certain architectures.

Now, since you're trying to do a scripting bridge thing, you probably cannot explicitly cast every case you run across, you want a general solution. All in all, this is not completely trivial, and unfortunately your code has to be specialized to each architecture you wish to target (e.g. i386, x86_64, ppc). Your best bet is probably to see how PyObjC does it. You'll also want to take a look at libffi. It's probably a good idea to understand a little bit more about how parameters are passed in C, which you can read about in the Mac OS X ABI Guide. Last, Greg Parker, who works on the objc runtime, has written a bunch of very nice posts on objc internals.

like image 183
Ken Avatar answered Sep 22 '22 07:09

Ken


objc_msgSend(object, sel_getUid("foo:bar:err:"), var, var2, errVar); 

If one of the variables is a float, you need to use @Ken's method, or cheat by a reinterpret-cast:

objc_msgSend(..., *(int*)&var, ...) 

Also, if the selector returns a float, you may need to use objc_msgSend_fpret, and if it returns a struct you must use objc_msgSend_stret. If that is a call to superclass you need to use objc_msgSendSuper2.

like image 32
kennytm Avatar answered Sep 23 '22 07:09

kennytm