This page contains some real-world(ish) examples of Ruby-FFI usage. It will generally present the C interface, along with the Ruby code to call the C code.
There are some other examples in the repository.
Common Usage
Let's imagine a C library interface like the following:
C interface:
/* mylibrary.h */
double calculate_something(int a, float b);
int error_code(void);
struct SomeObject* create_object(char* name);
double calculate_something_else(double c, struct SomeObject* obj);
void free_object(void* pointer_to_memory);
A possible C application might use this library as follows:
#include "mylibrary.h"
int main()
{
double c, d ;
int errcode ;
struct SomeObject *objptr ;
c = calculate_something(42, 98.6) ;
if ((errcode = error_code()) != 0) {
fprintf(stderr, "error calculating something: %d\n", errcode);
exit(1);
}
objptr = create_object("my object") ;
d = calculate_something_else(c, objptr) ;
free_object(objptr) ;
fprintf(stdout, "calculated %f\n", d);
exit(0) ;
}
Note that the code does not access the SomeObject's data members, but rather treats it as an opaque object -- that is, a pointer to a memory block that the application passes into other API calls, but doesn't operate on directly.
Now let's attach the library code to a Ruby FFI wrapper:
# mylibrary.rb
module MyLibrary
extend FFI::Library
ffi_lib "path/to/mylibrary.so"
attach_function :calculate_something, [:int, :float], :double
attach_function :error_code, [], :int # note empty array for functions taking zero arguments
attach_function :create_object, [:string], :pointer
attach_function :calculate_something_else, [:double, :pointer], :double
attach_function :free_object, [:pointer], :void
end
And then the same application logic, but this time in Ruby:
require 'ffi'
require 'mylibrary'
c = MyLibrary.calculate_something(42, 98.6) # note FFI handles literals just fine
if ( (errcode = MyLibrary.error_code()) != 0)
puts "error calculating something: #{errcode}"
exit 1
end
objptr = MyLibrary.create_object("my object") # note FFI handles string literals as well
d = MyLibrary.calculate_something_else(c, objptr)
MyLibrary.free_object(objptr)
puts "calculated #{d}"
Note about NULL Values
Note that you can pass a NULL pointer by passing nil
, as follows:
C code:
c = calculate_something_else(14.2, NULL);
Ruby code:
c = MyLibrary.calculate_something_else(14.2, nil)
Output Parameters with MemoryPointer
So far, you've seen a very simple C API. Most C APIs are a little more complicated, though.
Let's imagine the following function is also part of our MyLibrary interface:
/*
* find_first_match() looks for an object with name _name_ in the object cache.
* returns: the number of total matches,
* and sets the found_object pointer to the first SomeObject found
*/
int find_first_match(char* name, struct SomeObject** found_object) ;
This function is different in that it has an output parameter. See the following C code for usage:
struct SomeObject* objptr ;
int nfound ;
double result ;
nfound = find_first_match("jimbo", &objptr) ;
result = calculate_something_else(11.2, objptr) ;
The function is attached via FFI as follows:
attach_function :find_first_match, [:string, :pointer], :int
And here's the same sample usage translated into Ruby code:
objptr = FFI::MemoryPointer.new :pointer
nfound = MyLibrary.find_first_match("jimbo", objptr)
objptr = objptr.get_pointer(0)
result = calculate_something_else(11.2, objptr)
Strings as Output Parameters
More on output Parameters: Here are examples of methods which return a single string, and an array of strings. They were based on wrapping the Augeas library.
Single String
Assume the following method:
int aug_get(const augeas* aug, const char* path, const char** value);
Where the value of the get is the third parameter, and the return value tells you success or failure. The function is attached via FFI as follows:
module AugeasLib
extend FFI::Library
ffi_lib "libaugeas.so"
attach_function :aug_get, [:pointer, :string, :pointer], :int
end
And the sample usage of this would be:
def get(path)
ptr = FFI::MemoryPointer.new(:pointer, 1)
AugeasLib.aug_get(@aug, path, ptr)
strPtr = ptr.read_pointer()
return strPtr.null? ? nil : strPtr.read_string()
end
FFI::Pointer.read_string()
doesn't care about the string encoding used. To get a ruby string with proper encoding, you have to call force_encoding(encoding). Sample usage of this would be:
def get(path)
ptr = FFI::MemoryPointer.new(:pointer, 1)
AugeasLib.aug_get(@aug, path, ptr)
strPtr = ptr.read_pointer()
return strPtr.null? ? nil : strPtr.read_string().force_encoding('UTF-8') # returns UTF-8 encoded string
end
With *W
family of winapi functions you need to use #read_bytes(len * 2)
(because #read_string
stops on first \x00
).
Array of Strings
Assume the following method:
int aug_match(const augeas* aug, const char* path, char*** matches);
Where the value of the match is the third parameter, and the return value tells you success or failure and the size of the array. The function is attached via FFI as follows:
module AugeasLib
extend FFI::Library
ffi_lib "libaugeas.so"
attach_function :aug_match, [:pointer, :string, :pointer], :int
end
And the sample usage of this would be:
def match(path)
ptr = FFI::MemoryPointer.new(:pointer, 1)
len = AugeasLib.aug_match(@aug, path, ptr)
if (len < 0)
raise SystemCallError.new("Matching path expression '#{path}' failed")
else
strPtr = ptr.read_pointer()
strPtr.null? ? [] : ptr.get_array_of_string(0, len).compact
end
end
Structs
Now let's get more advanced -- let's access SomeObject's data members through a Ruby object.
/* my_library.h */
struct SomeObject {
struct SomeObject *next ;
char *name ;
double value ;
};
struct SomeObject *objptr1 ;
struct SomeObject *objptr2 ;
objptr1 = create_object("foobar");
printf("%s\n", objptr1->name); // => 'foobar'
int nfound = find_first_match("foobar", &objptr2) ;
printf("%s\n", objptr2->name); // => 'foobar'
free_object(objptr1);
Here's how to create a Ruby object to mirror this struct.
(Note: see [[Automatic Struct Layout]] for a way to automate layout offset calculations.)
module MyLibrary
class SomeObject < FFI::Struct
layout :next, :pointer,
:name, :string,
:value, :double
end
end
And here's the application code:
obj_ptr2 = FFI::MemoryPointer.new :pointer
obj_ptr1 = MyLibrary.create_object "foobar"
obj1 = MyLibrary::SomeObject.new(obj_ptr1) # wrap ruby object around C pointer
puts obj1[:name] # => 'foobar'
nfound = MyLibrary.find_first_match("foobar", obj_ptr2)
obj2 = MyLibrary::SomeObject.new(obj_ptr2.read_pointer()) # wrap ruby object around C pointer
puts obj2[:name] # => 'foobar'
MyLibrary.free_object(obj1.pointer); # equivalent to free_object(obj_ptr1)
Custom Memory Management
Now that we have a full-blown Ruby object to access C struct data members, wouldn't it be nice if we didn't have to take care of calling free_object each time we created a SomeObject?
You can do it! Inherit from FFI::ManagedStruct instead of FFI::Struct, and define a release() class method:
module MyLibrary
class SomeObject < FFI::ManagedStruct
layout :next, :pointer,
:name, :string,
:value, :double,
def self.release(ptr)
MyLibrary.free_object(ptr)
end
end
end
Yes, it's that easy. Now the code from the previous section could look like this:
begin
obj_ptr1 = MyLibrary.create_object "foobar"
obj1 = MyLibrary::SomeObject.new(obj_ptr1) # wrap ruby object around C pointer
puts obj1[:name] # => 'foobar'
end
# obj1 is now out-of-scope, and when it is GC'd,
# the SomeObject#release() method will be invoked with obj1 as its argument, allowing it to free the C struct's memory.
Note that for structs that are created with only native elements (like int's, etc.) may not need any cleanup method, as the structs themselves are cleaned up automatically at GC time. In the instance above we need a cleanup method since the struct refers to a pointer that needs to be cleaned up, to avoid a memory leak.
For both normal structs and "memory managed" you can explicitly call #free on them, if desired.
WARNING: Don't Use ManagedStruct When You Want Type-Casting
There's a potential pitfall when using FFI::ManagedStruct, which is that you may not always actually want to free underlying memory. For instance, in this case we don't want to free the underlying memory:
obj_ptr2 = FFI::MemoryPointer.new :pointer
nfound = MyLibrary.find_first_match("foobar", obj_ptr2)
obj2 = MyLibrary::SomeObject.new(obj_ptr2) # wrap ruby object around C pointer
puts obj2[:name] # => 'foobar'
However, as soon as obj2 goes out of scope, that underlying memory ''will'' be freed, despite the fact that the find_first_match() cache still has a pointer to it, leading to a probable segfault and FAIL. That's bad.
I've found it useful to define two Ruby classes, one inherited from ManagedStruct, for managing object lifetimes, and one inherited from Struct, used for typecasting pointers.
Here's an example of this:
module MyLibrary
module SomeObjectLayout
def self.included(base)
base.class_eval do
layout :next, :pointer,
:name, :string,
:value, :double,
end
end
end
class SomeObject < FFI::ManagedStruct
include SomeObjectLayout
def self.release(ptr)
MyLibrary.free_object(ptr)
end
end
class SomeObjectCast < FFI::Struct
include SomeObjectLayout
end
end
And here's some sample usage:
begin
obj_ptr1 = MyLibrary.create_object "foobar"
obj1 = MyLibrary::SomeObject.new(obj_ptr1) # NOTE use of managed struct class
puts obj1[:name] # => 'foobar'
obj_ptr2 = FFI::MemoryPointer.new :pointer
nfound = MyLibrary.find_first_match("foobar", obj_ptr2)
obj2 = MyLibrary::SomeObjectCast.new(obj_ptr2) # NOTE: use of typecast class
puts obj2[:name] # => 'foobar'
end
# obj1 and obj2 are now both out-of-scope. however, free_object() will
# only be invoked once, for obj1.
Callbacks
Many APIs use function pointers to manage "callbacks", which are functions-to-be-called-later. Ruby libraries commonly uses Procs or lambdas (let's call them '''closures''' here) for this, and FFI allows you to map closures to C function callbacks. Nice!
Again, here's our C API:
/*
* notifyWhenObjectWaggles callback:
* waggled: the object that just waggled
* waggle_size: how much the object waggled
* should return:
* non-zero integer to continue waggling, zero to cease waggling
*/
typedef int (*notifyWhenObjectWaggles)(struct SomeObject *waggled, int waggle_size) ;
void set_object_waggle_callback(struct SomeObject *waggler, notifywhenObjectWaggles callback) ;
Here's how it's used in C:
int waggle_callback(struct SomeObject *waggled, int waggle_size)
{
printf("object %s just waggled %d\n", waggled->name, waggle_size);
return(waggle_size > 10 ? 1 : 0);
}
int main()
{
...
struct SomeObject *p ;
p = create_object("foobar");
set_object_waggle_callback(p, waggle_callback);
...
}
Here's the Ruby declaration equivalent:
module MyLibrary
#... # same as before
# declare the callback type (the callback's function signature)
callback :object_waggled_callback, [:pointer, :int], :int
# declare the function signature that takes the callback as a param.
# note we use the callback type just like any builtin type here.
attach_function :set_object_waggle_callback, [:pointer, :object_waggled_callback], :void
And finally, the callback in action:
#
# it's a good policy to assign closures to a constant.
# more on this below.
#
MyLibrary::WaggleCallback = Proc.new do |waggled_ptr, waggle_size|
waggled = MyLibrary::SomeObjectCast.new waggled_ptr # note we use the typecast flavor
puts "object #{waggled[:name]} just waggled #{waggle_size}"
waggle_size > 10 ? 1 : 0
end
obj_ptr = FFI::MemoryPointer.new :pointer
obj_ptr = create_object("foobar")
obj = MyLibrary::SomeObject.new obj_ptr
set_object_waggle_callback(obj.pointer, MyLibrary::WaggleCallback)
WARNING: Be Careful Your Proc
Doesn't Get GC'd
Let's imagine the above example was written as follows:
begin
proc = Proc.new do |waggled_ptr, waggle_size|
waggled = MyLibrary::SomeObjectCast.new waggled_ptr # note we use the typecast flavor
puts "object #{waggled[:name]} just waggled #{waggle_size}"
waggle_size > 10 ? 1 : 0
end
set_object_waggle_callback(obj.pointer, proc)
end
At the end of the block, proc is out of scope and may be garbage-collected. This is really bad for your application, because as soon as the object waggles, the C library will be dereferencing a pointer that no longer points to your proc object.
You must be careful to ensure that your closures do NOT go out of scope while they are active!
The easiest way to guarantee this is to assign the proc to a constant, which will never go out of scope. This is what was done in the example above.
Using :varargs
Here's an example
require 'ffi'
module Hello
extend FFI::Library
attach_function 'printf', [:string, :varargs], :int
end
3.times { Hello.printf("cputs %s %d %x", :string, "yoyo", :int, 33, :int, 34)} # each one needs its own specifier of which type it is
Using typedef
You can define "aliases" for types using typedef current, new
. Once an alias has been defined, you can use it in a struct or function definition. This can make those definitions more descriptive and meaningful, or make it easier to deal with platform differences. Here's a contrived example:
module MyLibrary
extend FFI::Library
typedef :pointer, :id
# Define :status as an alias for :long on Mac, or :int on other platforms.
if FFI::Platform.mac?
typedef :long, :status
else
typedef :int, :status
end
class MyStruct < FFI::Struct
layout :a, :id, :b, :status
end
attach_function :doSomething, [:id, :int], :status
end