Bridging MRI, JRuby & Rubinius with FFI

Developing bindings for native extensions in any programming language is often a test of patience more than anything else, and even worse, the resulting interface is often platform specific and requires duplicate effort. Which is exactly what Foreign Function Interface (FFI) seeks to address: FFI is an interface that allows code written in one language to call code written in another language in a standard format.

Bridging MRI, JRuby and Rubinius

As a more practical example, consider an extremely popular Ruby extension: Hpricot. Written in C it works beautifully well with the MRI implementation of Ruby, but cannot be used by anyone in JRuby or Rubinius land. (Unless the author builds a custom version of the gem for each platform, which is what happened with Hpricot). Instead, thanks to the efforts of Evan Phoenix (Rubinius) and Wayne Meissner (JRuby), we now have a full blown, VM-independent FFI library. Let's walk through an example.

Exposing native libraries to Ruby

Based on libffi, the FFI gem (gem install ffi) is available for Ruby 1.8.6/7, and comes pre-packaged with both JRuby and Rubinius. The interface is consistent across all platforms, which means write once and it works everywhere! Let's create a custom native extension:

// Simple FFI example between C & Ruby
#include <math.h>

double power(double base, double power) {
  return pow(base, power);
}

double square_root(double x) {
  return sqrt(x);
}

// custom factorial function which we'll call from Ruby

long ffi_factorial(int max) {
  int i = max, result = 1;
  while (i >= 2) {
    result *= i--;
  }
  return result;
}

// Compiling shared library:
// gcc -shared -Wl,-soname,libsimplemath -o libsimplemath.so simple_math.c -lm

Here we've defined three simple math functions which we want to make use of in our Ruby application and compiled a shared library (.so) file. Now, to get access to these functions we simply have to specify the method signatures using FFI's DSL:

require 'ffi'

class FFIMath
  extend FFI::Library

  # load our custom C library and attach
  # FFI functions to our Ruby runtime
  ffi_lib "libsimplemath.so"

  functions = [
    # method # parameters        # return
    [:power, [:double, :double], :double],
    [:square_root, [:double], :double],
    [:ffi_factorial, [:int], :long]
  ]

  functions.each do |func|
    begin
      attach_function(*func)
    rescue Object => e
      puts "Could not attach #{func}, #{e.message}"
    end
  end

end

puts FFIMath.power(3,5)           # => 243.0
puts FFIMath.square_root(81)      # => 9.0
puts FFIMath.ffi_factorial(10)    # => 3628800
ffi-code.zip - Example source & benchmarks

FFI Benchmarks & Benefits

The immediate benefit of using the FFI gem is the simple DSL interface compared to other alternatives (SWIG, RubyInline, etc.) and the fact that the resulting code is automatically compatible with JRuby, Rubinius and MRI. At the moment, performance is still highly variable from platform to platform, but it's a work in progress and bound to improve. FFI projects are slowly popping up on GitHub and you should consider it for your next native extension as well!

Ilya GrigorikIlya Grigorik is a web ecosystem engineer, author of High Performance Browser Networking (O'Reilly), and Principal Engineer at Shopify — follow on Twitter.