123456789_123456789_123456789_123456789_123456789_

FFI is a fantastic tool for easily interfacing your Ruby code with native libraries. To help you quickly ramp up and become a happier and more productive FFI master, the following are a few of the fundamental concepts you'll want to understand in order to get the most out of FFI.

Overall Architecture

Using FFI, you can use native libraries from Ruby without writing a single line of native code. Your good friend FFI takes care of all of the cross Ruby implementation (MRI, JRuby, Rubinius, MacRuby, etc) and platform specific issues so that you can focus on writing and testing your Ruby code.

As great as this sounds, you just know there's something missing. Your spidey sense is right again.

As FFI is effectively a bridge between the multiple worlds of Ruby implementations and multiple platform types, you might suffer a bit of cognitive dissonance trying to pull all the pieces together. When we develop in Ruby we tend to think in higher level terms, and don't so much concern ourselves with the lower level issues. While FFI allows us to stay firmly rooted in Ruby, we also have to start thinking in lower level terms.

Core Components

FFI has a number of useful components. Investing the time to understand FFI's components and capabilities will pay off as you begin using FFI. That said, it's nice to have an idea which components you should look at first. Understanding the following core modules and classes is a great way to start getting FFI's capabilities:

Memory Management

When you're writing Ruby code you usually don't think about memory management. It's just taken care of for most of your use cases. However, when you're leveraging FFI, even though you're still developing in Ruby, you have to begin thinking more about these low-level issues.

Ruby has the concept of object references and garbage collection. In contrast C libraries have pointers and manual allocation and deallocation of memory. So you have to take care, that objects which provide memory referenced by a pointer, stays valid until the pointer is no longer used by the library. Therefore you have to keep references to these objects, even when they are no longer used by your ruby code.

So any use of :pointer, :string, :buffer_in, :buffer_out or :buffer_inout must be backed by keeping a reference in ruby. This is true for function arguments, pointer references in FFI::Struct as well as function pointers (callbacks). In other words: A pointer on C level to a Ruby object is not considered an object reference by the Ruby runtime and is therefore not sufficient to protect the object from being garbage collected.

String Memory Allocation

One such memory management consideration occurs when you try to integrate with C functions that keep a reference to a Ruby allocated string rather than making their own copy of the string's contents. Take the following rogueware snippet which occurs more often than we'd like to admit:

static char* my_name;

void bad_set_my_name(char* name) {
  my_name = name;
}

Mapped to ruby like so:

module Bar
  attach_function :bad_set_my_name, [ :string ], :void
end

This code assumes that its my_name reference to the Ruby string will remain valid during the time it's needed. So you have to store the ruby String object, that is passed to the C function, in a variable, that stays valid until my_name is no longer in use. Usually this is either a constant or an instance variable of a Ruby object, that has the same lifetime as the loaded library. If the Ruby object is not stored in a long enough living variable, the memory address held by my_name is no longer pointing to the string.

This is a safe call to the C function:

module Foo
  NAME = "Barney"
  Bar.bad_set_my_name(NAME)
end

If the string to be passed contains null bytes or the string data is modified on either the C or the Ruby side, you should copy the Ruby string to native memory. Then pass the pointer to that buffer to the C function instead of passing a raw string, and finally, update your original attach_function signature to use the :pointer rather than the original :string. For example:

# proper use of the bad C function from Ruby
module Foo
  NAME = FFI::MemoryPointer.from_string("Barney")
  Bar.bad_set_my_name(NAME)
end

# new Ruby mapping
module Bar
  attach_function :bad_set_my_name, [ :pointer ], :void
end

Using malloc/free

The FFI library provides ::FFI::MemoryPointer to allocate heap memory. To use the memory independently of the Ruby object lifetime, FFI::Pointer#autorelease= can be set to false. Then it can be used like malloc() in C. So the memory is not freed by the Ruby runtime, even when the Pointer object is garbage collected. This is sometimes necessary for libraries that take a memory buffer as an argument and then expect to manage that buffer's lifecycle.

buffer = FFI::MemoryPointer.from_string("content")  # Allocate 8 byte of memory to put string into plus zero termination
buffer = FFI::MemoryPointer.new(8)                  # Alternatively allocate 8 byte of memory and fill with zeros
buffer.autorelease = false
SomeLib.function_that_will_manage_buffer(buffer)

It's still possible to free the buffer manually by calling FFI::Pointer#free, when it's no longer in use by the library.