ze goggles

Ruby-FFI recipes

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.

Unfortunately, FFI is not documented at all, except for one or two outdated blog posts (one of them is "On the Rubinius FFI", then there are some examples on the kenai wiki).

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.

recently on twitter

currently reading