In the session I presented at railsclub I finished off by showing how to use FFI to call into Objective-C (If you weren’t there on then my slides are online - I would recommend browsing through the section on FFI). I didn’t go into any detail because it was the end of what I think was a pretty technically intense session.
This definitely comes under the category of “doing something to prove it can be done” rather than anything more serious, but if you’d like to know how it works then read on!
The idea of this bridge is that we wrap instances of
NSObject with a ruby object that forward messages to the underlying object. I’m going to explain how the bridge works - you might want have a copy of the file open for reference.
1 2 3 4 5 6 7 8
This section is straightforward: it loads the Objective-C runtime library (libobjc) and the Foundation and Appkit framework. These frameworks are only there because the demo I did happens to use those frameworks. If you wanted to call other frameworks then you’d need to add them to the list. Them some typedefs are setup to make the rest of the code more readable.
Now it’s time to attach some functions:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
In order these are:
Logs a format string to the console - for debugging only.
Looks up a class by name, returning a instance of
Converts a string into a selector (the runtime APIs use selectors to identify methods).
This is the core objective-C dispatch function:
[some_object doThisWith:foo and:bar] turns into a call to
some_object, @selector(dothisWith:and:),foo,bar). This is basically the same as ruby’s
:varargs in the argument list tells FFI that this function (and thus the corresponding ruby method) takes a variable number of arguments.
These are used for methods that return float/doubles. On i386 this requires calling
objc_msgSend_fpret instead of
objc_msgSend. On x64 we call the same function in all cases, but we need separate FFI bindings so that the return value is picked correctly.
The next set of functions are how we get method information out of the Objective-C runtime:
respond_to?. This returns a uchar because the FFI
:bool type corresponds to the
bool type (from C99) whereas the Objective-C
BOOL type is an unsigned char. The pitfall this creates is that you need to check the value for zero-ness rather than truthyness.
Returns a pointer to the
Method structure for the method.
Returns the number of arguments the method expects (ie the number of
: in the selector).
Retrieves the return type (more about types later).
Retrieves the type of the n-th argument.
Retrieves the class for a given object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
This is our proxy object class. It is a subclass of
FFI::Pointer and the pointer value will be the address of the object. This means that when pass
self to a function expecting a pointer argument we are passing though object’s address.
The first step is extracting the selector and arguments from my abuse of ruby’s hash notation. I’m expecting
[some_object doThisWith:foo and:bar] to result in
objc_send being invoked like so
Or for a method with no arguments (eg
extract_arguments_and_method_name unpacks this string/hash into an array of argument values and the selector name (i.e.
doThisWith:and: in the first example).
Then we convert this string into the actual selector object that the runtime APIs expect. Once
class_respondsToSelector has confirmed that the method actually exists we call
Before we peek inside
invoke_selector, a brief digression on how FFI’s support for varargs work. When you have declared a function as taking varargs then each of the variable arguments must be preceded by its ffi type (:pointer, :string, :int and so on). For example you could call the libc
printf like so
1 2 3 4 5 6 7 8
So in order to call
objc_msgSend we need to find the ffi type for each argument. We also need to workout the return type so we know which version of
objc_msgSend to call.
Objective-C type signatures
This is where
method_getArgumentType come into play.
method_getNumberOfArguments tells us how many arguments to expect and
method_getArgumentType allows you to grab the signature for a particular argument.
A signature is a string describing a type. The
<objc/runtime.h> header defines what each of the symbols mean, for example
@ means object and ’d’ means double. Some of these can be combined with
r, which means
objc_signature_to_ffi_type method is just a big switch statement mapping these various types onto ffi types (it’s incomplete - I build what I needed).
1 2 3 4 5 6 7 8 9 10 11
The first step is to get the ffi types we need - that is what
signature_for_selector does (using the previously mentioned runtime functions and
objc_signature_to_ffi_type). At the end of this
return_type_signature will be something like
@ (if the method returns an object) and argument types will be something like
[:pointer, :pointer, :int]
We then zip the argument types with the argument values and use the return type to call the appropriate variant of
objc_msgSend. In the float/double case we do no further work (since we have told ffi the appropriate return value) but in the default case we may need to convert the return value from the pointer returned to a string, integer, char etc. as appropriate (
coerce_return_value does this).
Joining the dots
Lastly a little magic is done with
method_missing. If you try to access
const_missing will use
objc_getClass to get the
Class (which is itself an object), wrap it in an
Objc::Object and set the constant for future use. If you then hit
method_missing on that wrapper then it will call
The code I ran during the session was
1 2 3
The steps that happen are
Objc::NSStringcauses the constant missing hook to fire and creates a wrapper for the
method_missingis called on this class. This is converted to a call to
objc_send(:stringWithCString => "/Users/fred/Desktop/sound.mp3", :encoding => 4), 4 being the value of NSUTF8Encoding
- A wrapper object is created around the returned NSString
- A wrapper is created for the
allocis called on the wrapper, triggering method missing, and then
initWithContentsOfFileis called on the object returned by
- the returned
NSSoundis wrapped and stored in
playis called on
NSSound. This takes no arguments so is mapped onto a call to
There’s an awful lot this code doesn’t do:
- considering solely the task of method invocation it doesn’t deal with things like variable arguments or structures as arguments/return values (You need to use
objc_msgSend_stretto return large structs).
- it doesn’t deal with the Objective-C equivalent of
- the object hierarchy is confusing: everything appears to be an instance of
Objc::Objectwhereas it would probably less surprising if they appeared to be instances of the appropriate classes. This would require redefining methods like
classand so on. Equally this might introduce an amount of complication that is unwarranted.
- if you were to use this in a more long running situation you would need to handle memory management, in particular autorelease pool creation/draining.
Thinking more widely this snippet doesn’t allow you to define or extend classes. This should be a matter of calling the right runtime functions to create the classes and then calling
class_addMethod to add methods. Method bodies are specified as function pointers, so this would require the use of FFI callbacks. I suspect it would be best to work out what to do with the object hierarchy before embarking on this.