Encapsulating a C library using Ruby ffi

Motivation

Recently I read an article called Ruby Bindings and Extensions where the author shows how in his company they were given the task of encapsulating a C library called H3 using Ruby and after evaluating the available options they decided to use ffi to do this work.

After reading the article I decided that I wanted to try this. Probably I won’t use this knowledge soon in my current project, but who knows, I think it’s a good tool to keep in mind.

In this essay, I want to share the process that I followed to experiment with ffi.

Creating a C library for experimenting

I had a while without writing something in C. So I decided to bring back my rusty C programming knowledge, For this task, I decided to create a super simple library for managing PGM images with only four functions.

Compiling a shared library

Well, the first part was easy only bring back some memory allocation, and pass by reference concepts. The second part was remembering how to create a shared library with my code. I looked on internet and I found a very well detailed article that explains what is a shared library and how this works here is the link if you are interested on learning more about it Shared libraries with GCC on Linux.

To cover this requirement I added a new task inside my makefile, that create the shared library from my object file:

shared:
	mkdir -p lib
	gcc -c -fpic ./pgmlib/lib/pgm.c
	gcc -shared -o lib/pgm.so pgm.o

Creating the Ruby ffi Layer

After creates my C library I started to build the ruby logic that wraps my native code.

The first part was downloading the ffi gem. After that, although this is only a proof of concept and my library has only 4 functions I decided to take as reference the project created from the Stuart engineering team and use a similar structure in my project.

I created a Structs module that maps the C Struct with a FFI::Struct.

C Struct

typedef struct Pgm {
  char* magicNumber;
  int width;
  int height;
  int maxVal;
  int** image;
} Pgm;

Ruby FFI::Struct

class Pgm < FFI::Struct
  layout :magic_number, :string,
         :width, :int,
         :height, :int,
         :max_val, :int,
         :image, :pointer
end

After That, I created a Base module where I add all the FFI base configuration. Here I specify the route for the shared library and incude the FFI dependency.

module Base
  def self.extended(base)
    lib_path = File.expand_path(__dir__ + '/../../../ext/src/lib')
    base.extend FFI::Library
    base.include Structs
    base.ffi_lib "#{lib_path}/pgm.so"
    base.typedef :pointer, :pgm
  end
end

After that, I added a Functions module that handles the wrapping of the C library functions.

module Functions
  extend PGM::Bindings::Base

  attach_function :load_pgm, :loadPgm, %i[pgm string], :pgm
  attach_function :save_pgm, :savePgm, %i[pgm string], :pgm
  attach_function :invert_colors, :invertColors, %i[pgm pgm], :void
end

And finally I added the entry module entry point pgm.rb

require 'ffi'

require_relative './pgm/bindings'
require_relative './pgm/functions'

module PGM
  extend Functions
end

Testing my brand new ruby module

For this I did the following steps:

>> require './lib/pgm'
=> true
>> pgm = PGM::Bindings::Structs::Pgm.new
=> #<PGM::Bindings::Structs::Pgm:0x00007f9b42806cb0>
>> PGM::Functions.load_pgm(pgm, './lib/feep.pgm')
=> #<FFI::Pointer address=0x0000000000000000>
>> pgm_out = PGM::Bindings::Structs::Pgm.new
=> #<PGM::Bindings::Structs::Pgm:0x00007f9b40145e78>
>> PGM::Functions.invert_colors(pgm, pgm_out)
=> nil
>> PGM::Functions.save_pgm(pgm_out, './lib/feep_out.pgm')
=> #<FFI::Pointer address=0x0000000000000000>

feep.pgm feep.pgm

feep_out.pgm feep_out.pgm

Conclusion

This was only a proof of concept but I have learned a lot of new things, I think ffi is an amazing alternative when you have native code and you want to call it inside your application. If you want to see all the code and play with it here is the repository pgm_ffi.