Wrapping C libraries with Ruby using SWIG

At Gusto, most of our application is written in Ruby, but our tax calculations are done in a C library. Creating Ruby extensions using SWIG (Simplified Wrapper and Interface Generator) has saved us countless hours by automatically generating glue code between Ruby and C.

SWIG can be used to generate the glue code between C/C++ and various scripting languages, such as Perl, Python, Ruby, and Tcl. In this post, we’ll create a Ruby gem that wraps a simple C library.

So take a swig of beer, coffee, awesome juice or whatever you like, and let's get started! First step is to download and install SWIG.

Next, let's create a new gem:

$ bundle gem payroll_calculator

We’ll start with a simple C library:

/* ext/libpayroll.h */
double calculate_sui_tax(const double amount, const double rate);  
/* ext/libpayroll.c */
#include "libpayroll.h"

double calculate_sui_tax(const double amount, const double rate)  
{
  double tax = amount * rate;
  return tax;
}

Now we need to wrap our C library! Interface files are the input to SWIG. To tell SWIG to wrap the above function, we create the following interface file.

/* ext/libpayroll.i */
%module libpayroll
%{
  #include "libpayroll.h"
%}

extern double calculate_sui_tax(const double amount, const double rate);  

This tells SWIG to create a Ruby module called Libpayroll, and all the functions listed will be wrapped. To run swig against the interface file, type:

$ swig -ruby ext/libpayroll.i

This will generate ext/payroll_calculator_wrap.c, which can be compiled into a shared library used in Ruby. To compile, we’ll need an extconf.rb, to configure a Makefile to build the extension.

# ext/extconf.rb
require 'mkmf'  
create_makefile('libpayroll')  

To create the extension:

$ cd ext
$ ruby extconf.rb
$ make
$ cp libpayroll.bundle ../lib

Note: if you’re on a linux machine, you will probably see a .so file instead of a .bundle file

Now we can test this out. Go back to the root directory, and fire up irb:

$ irb -Ilib -rpayroll_calculator
2.0.0-p247 :001 > Libpayroll.calculate_sui_tax(9000, 0.34)  
 => 3060.0 

We can simplify this with a few rake tasks:

# Rakefile
require 'bundler/gem_tasks'  
require 'rake/clean'

CLEAN.include('ext/**/*{.o,.log,.so,.bundle}')  
CLEAN.include('ext/**/Makefile')  
CLOBBER.include('lib/*{.so,.bundle}')

desc 'Build the libpayroll C extension'  
task :build_ext do  
  Dir.chdir("ext/") do
    sh "swig -ruby libpayroll.i"
    ruby "extconf.rb"
    sh "make"
  end
  cp "ext/libpayroll.bundle", "lib/"
end  

require 'rake/clean' will give you two additional rake tasks, rake clean and rake clobber, which will help you remove unnecessary files after building your extension. The next three lines tell the rake tasks which files to include when cleaning and clobbering.

Additionally, we’re adding a task rake build_ext, which will generate the C extension from the SWIG interface file.

Now that we have the basics setup, let's dive a bit deeper into some more features of SWIG. Let us now say that we have a function in libpayroll.c like this:

/* libpayroll.c */
int calculate_income_tax(const double amount, double *tax, double *rate){  
  if(amount >= 0 && amount <= 1000) {
    *rate = 0.25;
  } else if (amount > 1000 && amount <= 5000) {
    *rate = 0.30;
  } else if (amount > 5000) {
    *rate = 0.35;
  } else {
    return 1;
  }

  *tax = *rate * amount;
  return 0;
}

With this function, we have two return values: the tax and the amount. In Ruby, we would probably want this returned as a hash. SWIG typemaps make this a piece of cake!

Our function currently takes three arguments, but the last two are pointers, and we only care about their values after we call the function, and therefore should really be return values. In Ruby, we want calculate_income_tax to take one argument. To change how SWIG will wrap this function, we first use the in typemap:

/* ext/libpayroll.i */
%typemap(in, numinputs=0) (double *tax, double *rate) {
  $1 = (double *)malloc(1 * sizeof(double));
  $2 = (double *)malloc(1 * sizeof(double));
};

This means to take a function signature that has double *tax, double *rate and ignore them. $1 and $2 are then what are actually sent into the C function. Here, we just allocate memory - in this case the size of one double.

Next we handle the return values. We can use the argout typemap to modify the output of the function:

/* ext/libpayroll.i */
%typemap(argout) (double *tax, double *rate) {
  if(result == 0) {
    $result = rb_hash_new();
    rb_hash_aset($result, rb_str_new2("tax"), rb_float_new(*$1));
    rb_hash_aset($result, rb_str_new2("rate"), rb_float_new(*$2));
  } else {
    $result = Qfalse;
  }
}

Whatever we assign to $result is what ends up being returned. We can check the original return value by looking at the result variable. If the result is 0, meaning success, we initialize a new Ruby hash. *$1 and *$2 refer to double *tax and double *rate, respectively. Calling rb_float_new creates a new Ruby float object from the C double. Finally, we assign the values to keys tax and rate in the Ruby hash.

If the result is not zero, we want to return false in Ruby, which in the Ruby C API is Qfalse. (True and nil are Qtrue and Qnil, respectively)

When we create Ruby objects, the Ruby garbage collector will take care of releasing the memory when it is no longer needed. However, the memory from our initial malloc calls need to be released to avoid memory leaks. This can be done in the freearg typemap.

/* ext/libpayroll.i */
%typemap(freearg) (double *tax, double *rate) {
  free($1); 
  free($2);
}

Finally, we add the method signature so that SWIG knows to wrap it:

/* ext/libpayroll.i */
int calculate_income_tax(const double amount, double *tax, double *rate);  

Note that all typemaps for a given function must be located above the method signature in the interface file.

Let's try it out!

$ rake build_ext
$ irb -Ilib -rpayroll_calculator
2.0.0-p247 :001 > Libpayroll.calculate_income_tax(500)  
 => {"tax"=>125.0, "rate"=>0.25} 
2.0.0-p247 :002 > Libpayroll.calculate_income_tax(2000)  
 => {"tax"=>600.0, "rate"=>0.3} 
2.0.0-p247 :003 > Libpayroll.calculate_income_tax(9500)  
 => {"tax"=>3325.0, "rate"=>0.35} 
2.0.0-p247 :004 > Libpayroll.calculate_income_tax(-100)  
 => false 

This only scratches the surface of SWIG. For more details, check out documentation.

For the complete source code from this tutorial, fork on GitHub.