using objc_msgSend to call a Objective C function with named arguments
Asked Answered
S

3

26

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

Separator answered 4/4, 2010 at 7:8 Comment(2)
Objecrtive-C does not have named arguments. It has arguments interleaved with the method name. (Named arguments imply that dropping the names is an option, which it isn't).Petromilli
Given that markus just wanted to create a generalized bridge for scripting access and not a hyper performant c level bridge, couldn't he just use a use NSInvocation like here: cocoawithlove.com/2008/03/…Unused
B
21
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.

Because answered 4/4, 2010 at 7:17 Comment(5)
This is close, but will not work as expected with methods taking certain types. For example, suppose the method is declared to take a float for one of the parameters. objc_msgSend is declared as a varargs function, and any float passed in to it will be promoted to double. The impl of objc_msgSend will then just jump to the method that is expecting a float, and it won't work. See my answer for details.Whipstall
Ken is exactly correct; the vararg handling ABI & float/double handling ABIs can be extremely perverse, depending on CPU.Petromilli
Regarding the edit: not all struct returns on all architectures use objc_msgSend_stret. It varies by architecture and by what's in the struct.Whipstall
Annnnnd that reinterpret cast thing won't work reliably either. ;-) I think the point there is to cast to int because int and float have the same size? Unfortunately ints and floats are not passed the same way on all architectures even when they have the same size. Casting to float does no good because it just gets promoted to double. Really, you need to cast the function pointer.Whipstall
(The reinterpret cast above is also a strict aliasing violation. Okay, I'm done! Sorry for harping!)Whipstall
W
46

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.

Whipstall answered 4/4, 2010 at 8:50 Comment(1)
casting between function pointer types and void * is not allowed in standard CGlarum
B
21
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.

Because answered 4/4, 2010 at 7:17 Comment(5)
This is close, but will not work as expected with methods taking certain types. For example, suppose the method is declared to take a float for one of the parameters. objc_msgSend is declared as a varargs function, and any float passed in to it will be promoted to double. The impl of objc_msgSend will then just jump to the method that is expecting a float, and it won't work. See my answer for details.Whipstall
Ken is exactly correct; the vararg handling ABI & float/double handling ABIs can be extremely perverse, depending on CPU.Petromilli
Regarding the edit: not all struct returns on all architectures use objc_msgSend_stret. It varies by architecture and by what's in the struct.Whipstall
Annnnnd that reinterpret cast thing won't work reliably either. ;-) I think the point there is to cast to int because int and float have the same size? Unfortunately ints and floats are not passed the same way on all architectures even when they have the same size. Casting to float does no good because it just gets promoted to double. Really, you need to cast the function pointer.Whipstall
(The reinterpret cast above is also a strict aliasing violation. Okay, I'm done! Sorry for harping!)Whipstall
A
3

objc_msgSend(obj, @selector(foo:bar:err:), var, var2, &errVar);

Arborization answered 4/4, 2010 at 7:17 Comment(1)
Won't work for reasons described by Ken... you need to create a specifically typed function pointer.Petromilli

© 2022 - 2024 — McMap. All rights reserved.