Introduction
Ruby is a great programming language, but like all programming languages it is not suitable for everything. Sometimes it can make sense to use native libraries on the platform or C to improve the performance of slow Ruby code.
This post will explore calling C libraries and functions from Ruby. Although the methods mentioned in this post are not limited to just that, it is a very common use case. Ruby has a few ways of integrating with native code. This post will look at native extensions, foreign function interface, and inline.
Native Extensions
Native extensions provide a method of interfacing with C code written in a certain way to make it accessible from Ruby. Typically, extensions are written in C, then built, loaded and called from Ruby. This is commonly used to interact with platform libraries by providing a wrapper or an additional API layer for them. RMagick and nokogiri are among the most popular gems that use extensions. The notable disadvantages of this approach are that it is tightly coupled with the Ruby VM implementation, the extension must be built when installing or updating, and also it is in C. Typically, a Ruby script is used to produce a Makefile, and the Makefile is then used to build the extension.
Extensions example
In a trivial example, there are a couple of files:
$ ls
extconf.rb helloext.c
extconf.rb contains this:
ruby
require 'mkmf'
dir_config('helloext')
create_makefile('helloext')
Also, helloext.c contains this:
c
#include "ruby.h"
VALUE hello_ext(VALUE self) {
printf("Hello, extensions!\n");
return Qnil;
}
void Init_helloext() {
rb_define_global_function("hello_extensions", hello_ext, 0);
}
Building and execution goes like this:
$ ruby extconf.rb
creating Makefile
$ make
compiling helloext.c
linking shared-object helloext.bundle
$ ls
Makefile extconf.rb helloext.bundle helloext.c helloext.o
This extension can be loaded and utilised from Ruby code:
$ irb
2.1.2 :001 > require './helloext.bundle'
=> true
2.1.2 :002 > hello_extensions
Hello, extensions!
=> nil
Foreign Function Interface
The Foreign Function Interface (FFI) is another method of interfacing with native binaries. It utilises libffi installed on the platform, and does not require compilation. It is very simple to use this for loading a platform library and calling its functions. There are two common ways of using the FFI in Ruby; the ffi gem and Fiddle. Fiddle is part of the standard library and ffi is a gem. While the ffi gem has a wiki with lots of information, the Fiddle documentation is not extensive.
The main advantage of this approach is that it is cross-platform and not limited to MRI, since it is not tightly coupled with the Ruby implementation. Another advantage is that there is no need to build binaries once FFI is set up and functioning.
FFI example
The GitHub page for ffi contains this example:
ruby
require 'ffi'
module MyLib
extend FFI::Library
ffi_lib 'c'
attach_function :puts, [ :string ], :int
end
MyLib.puts 'Hello, World using libc!'
An example with Fiddle which does the same might look like this:
ruby
require 'fiddle'
libc = Fiddle.dlopen('/usr/lib/libc.dylib')
libc_puts = Fiddle::Function.new(libc['puts'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
libc_puts.call("Hello, Fiddle!")
As they do not require compilation, they can be run directly like this:
$ ruby ffi-example.rb
Hello, World using libc!
$ ruby fiddle-example.rb
Hello, Fiddle!
Inline
RubyInline is a gem that makes it possible to embed “inline” C code in Ruby. Typically C code is embedded in string literals and built at runtime. The notable advantage is that it is trivially simple to embed a little C code with RubyInline. The main disadvantages are that it requires compilation at runtime, and that code embedded in strings can be hard to work with.
RubyInline example
ruby
require "inline"
class HelloInline
inline do |hello|
hello.c '
void hello_inline() {
printf("Hello, RubyInline!\n");
}
'
end
end
HelloInline.new.hello_inline
RubyInline takes care of building the binary, so this can be called directly too.
$ ruby inline.rb
Hello, RubyInline!
Final Thoughts
Although I love how easy it is to embed a small C function with RubyInline, I would not like to embed C code in strings, or to compile it at runtime. Previously I have only used extensions, but after writing this post, I would probably use ffi over extensions.