If you like Ruby, you probably enjoy metaprogramming and code generation. If you haven’t used SWIG, you’ll enjoy that special feeling when you write a couple of simple files and suddenly a C library Just Works in Ruby.
I have a sound synthesis engine in C that I use for live performance, and I would like to control it with a gamepad. The freely available Joystick Wrapper Library has worked well for me as a C library, but I’d like to be able to use it from Ruby to easily make changes on the fly while performing.
So I decided to try to generate a SWIG wrapper for it. I know from experience that some libraries are tricky to use to SWIG, but libjsw worked very well and serves as a nice illustration of some of SWIG’s basic features.
You’ll have to have libjsw, SWIG, and Ruby installed. Note that your SWIG has to be built against the Ruby you wish to use.
The first thing is to write a SWIG interface file, which is language-neutral.
1 %module jsw 2 3 %{ 4 #include <jsw.h> 5 %} 6 7 %include cpointer.i 8 %include "/usr/include/jsw.h" 9 10 %pointer_functions(int,intp);
Line 1 specifies the name of the Ruby (or any other language)
module that will be generated. Language-specific conventions will
be respected; with Ruby’s capitalization rules this will become
module Jsw.
Lines 3-5 will be passed to the compiler as-is when building the library. Here, we pass in the libjsw C header file.
This works fine for the usual call-by-value parameters, but C’s simulation
of call by reference with pointers (see below for a note on what
I mean by “simulation”) requires some special handling, which is
why SWIG provides the cpointer.i library. I include it in line 7
create a pointer function in line 10. This reads “make functions
with the name intp to handle pointer to int”. More on this below.
Line 8 is where the magic happens! This one statement says “wrap everything you find in this include file.”
This is the standard Ruby way of creating extensions.
1 require 'mkmf' 2 3 have_library('jsw') 4 create_makefile('jsw')
Line 1 required the mkmf library for generating extensions.
Line 3 makes sure to include libjsw library when the extension is compiled.
Line 4 writes out a Makefile.
1 % swig -ruby jsw.i 2 % ruby extconf.rb 3 % make
Line 1 will generate a file jsw_wrap.c that will be used
to build the Ruby extension. You might get warning like
/usr/include/jsw.h:112: Warning(801): Wrong class name (corrected to `Js_axis_struct')
This is telling you that Ruby can’t use the original C struct name for a Ruby class, and is changing it appropriately.
Line 2 generates a Makefile to build the extension out of jsw_wrap.c.
A file mkmf.log is also created with some details on what mkmf did
to create the Makefile.
Line 3 executes the build. You should see some gcc (or whatever compiler
you’re using) lines go by, and you should end up with jsw.so. And
that’s it! jsw.so is the extension!
Now just require the Jsw module to use it:
1 require 'jsw' 2 include Jsw
The full source code is available below, but have a look at the C and Ruby versions of this snippet that fetches the current set of joystick attributes:
1 const char * calib = JSDefaultCalibration; 2 js_attribute_struct * js_attrib; 3 int total_js_attribs; 4 5 js_attrib = JSGetAttributesList(&total_js_attribs, calib);
1 calib = JSDefaultCalibration 2 js_attrib = Js_attribute_struct.new 3 total_js_attribs = new_intp 4 5 js_attrib = JSGetAttributesList(total_js_attribs, calib) 6 7 pp intp_value(total_js_attribs) 8 pp js_attrib
Line 1:
The C-style string calib has become a Ruby String
containing the string in the constant JSDefaultCalibration.
Line 2:
The pointer to C struct js_attribute_struct has become the
Ruby object of class Js_attribute_struct, which is instantiated
with its new() method. Note that a pointer is used in C so that
when it is passed into a function, the function can
modify the referenced struct. This works similarly in Ruby because an
object reference is passed in, and the function can modify the
referenced object. SWIG refers to this as using
“opaque objects” in the target language.
The fields of the C struct become attributes of the
target language object.
Line 3:
But look at lines 3 and 5. There’s a problem here, in that in C an
integer variable total_js_attribs is used, and then its
address is taken as the argument to JSGetAttributesList so
that the function can modify its value. It doesn’t make sense
to use a Ruby object in this case, because a variable with a
single value is wanted. But simply passing in a Ruby variable
to the function means that variable cannot be written to; it
will be passed by value.
This is where the cpointer.i library that was included in the
interface file comes in. Line 3 of the Ruby version creates
a special Ruby variable using the new_intp() method. This
is passed in to the function, and when read with the intp_value()
method, has worked like a C variable passed by reference.
I haven’t peeked at the internals but I assume that a Ruby
object is being used behind the scenes.
To sum up:
A C struct becomes a Ruby class
C variables passed “by reference” using pointers are handled
using functions specified from the cpointer.i library.
Source code is available as a tarball.