The Ruby FFI (Foreign Function Interface) provides a neat unified API for interfacing different flavours of Ruby (MRI, JRuby, Rubinius) with native C code. After reading Charles Nutter's recent blog post "fork and exec on the JVM? JRuby to the Rescue!" I decided to have a play with it, mainly to launch console programs directly from a jirb session.
So here a quick blog posts with some common ffi recipes. Say you wanted to use fork+exec, as demonstrated by Charles, but you want to use execlp/execvp, two functions which use the path to look up the executable (as hinted at by the trailing p in their names).
Here's the function definition for execlp:
int execlp(const char *file, const char *arg0, ... /*, (char *)0 */);
A string (file), another string (arg0) followed by varargs (...), followed by NULL, returning int. This translates into the following FFI glue code:
module Exec extend FFI::Library attach_function :execlp, [:string, :string, :varargs], :int end
Pretty straightforward, right? Now you can use the C function from Ruby:
Exec.execlp("vim", "vim", [:string, "/tmp/foo", :pointer, nil])
Notice how the varargs get passed as array with [:type, :value] pairs. This is necessary because ruby-ffi cannot automatically infer types from your calling code. Also notice that you need to null-terminate the array yourself. However, ruby-ffi does some magic for you behind the scenes (for example allocating temporary memory for C strings / pointers and copying the contents of your Ruby strings into it). Don't overestimate this though: FFI is (by design) a very low-level library.
My first advice: if you've got a choice between a varargs method and *char, use the variadic function, it is easier to interface with. Now to execvp, which does the same as execlp, but using an argument vector (v) instead of a list (l). The C function definition reads:
int execvp(const char *file, char *const argv);
Which translates to:
module Exec extend FFI::Library attach_function :execvp, [:string, :pointer], :int end
One string, one pointer. The pointer is the tricky bit. You can not just pass an array of Ruby strings like so:
Exec.execvp("vim", ["vim", "/tmp/foo", nil])
No, you need to manually create a pointer pointing to an array of pointers (yay!):
strptrs =  strptrs << FFI::MemoryPointer.from_string("vim") strptrs << FFI::MemoryPointer.from_string("/tmp/foo") strptrs << nil # Now load all the pointers into a native memory block argv = FFI::MemoryPointer.new(:pointer, strptrs.length) strptrs.each_with_index do |p, i| argv[i].put_pointer(0, p) end Exec.execvp("vim", argv)
The fun of C ! Pointers to other pointers ! And it will even randomly crash if you make a mistake!
The varargs version is a lot simpler, and has less scope to shoot yourself in the foot (well, you're now using C, so technically it's too late anyway). The good news: most people probably won't need to write FFI code, eventually the important C-based tools / libraries will have an FFI-layer provided by someone else. However, as FFI is quite new this is not the case for all of them yet.