I expressed a desire to be able to test my C code by compiling it up as a Ruby extension. However I was unsure about the capabilities of such a C extension. I think in retrospect I had been confused by the complexity of the wrappers that swig generates, things are a lot easier than the 2000 lines of swig wrapper code would indicate.
So here's a howto covering creating a simple Ruby C extension running under cygwin on windows. I'm building cygwin extensions because I want to use the gpp compiler as I am also using a variant of this to generate source for an ARM microcontroller. The cygwin installation seems to coexist nicely with a standard windows installation and the cygwin build seems to be up to date. One has to be careful that the correct ruby is being invoked in the right circumstances: my setup is finding the standard windows version first on the path so I have to invoke the cygwin ruby and irb explicitly, e.g.
From Windows CMD prompt:
\cygwin\bin\ruby
From bash:
/bin/ruby
I have no idea how good a version of ruby the cygwin one is and I'm not inclined to test it out (UPDATE: 24-1-08 I've found nothing wrong with the cygwin ruby, it can run rails and everything else I've thrown at it). When I'm developing rails apps I would rather stick with the standard Windows version which is where I install all my gems. My cygwin is a vanilla ruby standard library (UPDATE: you can install gems into the cygwin ruby using /bin/gem at a bash prompt and they install just fine unless they require libraries that are not available in the cygwin repository).
Install the Tools
You will need to install Cygwin and, in particular, the following modules:
- gcc
- make
- ruby
The windows distribution of Ruby is compiled in Visual C++ 6 and so you cannot compile extensions for it using gcc. This howto creates extensions for the cygwin build of ruby.
Create a makefile
Ruby contains the tools to build the makefile for you. Create a file called 'extconf.rb' and in it put the following:
# Loads mkmf which is used to make makefiles for Ruby extensions require 'mkmf' # Do the work create_makefile('try')
here 'try' is the name of my new extension. extconf.rb HAS to be run using the cygwin ruby, not the windows ruby. The easiest way to get it all working is to go into bash and run it with:
/bin/ruby extconf.rb
If you get a silly error like
/usr/bin/ruby: no such file to load -- ubygems (LoadError)
this can be fixed by installing ruby gems in your cygwin ruby installation.
Create the Source File
The source to the extension goes into 'try.c' which looks like this:
1 /* 2 * Just try 3 */ 4 5 #include <stdio.h> 6 #include "ruby.h" 7 8 VALUE tryclass_initialise( VALUE self) 9 { 10 return self; 11 } 12 13 VALUE tryclass_doit( VALUE self) 14 { 15 printf( "doing it\n"); 16 return Qnil; 17 } 18 19 VALUE tryclass_whoops( VALUE self) 20 { 21 rb_raise( rb_eRuntimeError, "Whoopsie Daisy"); 22 return Qnil; 23 } 24 25 void Init_try( void) 26 { 27 VALUE oMyClass = rb_define_class( "TryClass", rb_cObject); 28 rb_define_method( oMyClass, "initialize", tryclass_initialise, 0); 29 rb_define_method( oMyClass, "Doit", tryclass_doit, 0); 30 rb_define_method( oMyClass, "Whoops", tryclass_whoops, 0); 31 }
This is creates a class with just three methods:
- initialize
- initialization (sic) method that is called when the object is created.
- Doit
- silly method that just prints something
- Whoops
- the meat, a method that will raise a runtime error.
This should build sweetly:
make
The extconf should have guessed that the source code was in a file called 'try.c' and filled the makefile out accordingly.
Testing It
the whole point of this exercise is to be able to test my C using Ruby's unit test framework so this next bit better be good.
1 require 'test/unit' 2 require 'try.so' 3 4 class TestTry < Test::Unit::TestCase 5 def test_it 6 o = TryClass.new 7 o.Doit 8 end 9 10 def test_whoops 11 # 12 # Raise an error in the extension. 13 # 14 o = TryClass.new 15 oException = assert_raises RuntimeError do 16 o.Whoops 17 end 18 assert_equal "Whoopsie Daisy", oException.message 19 end 20 end
A thing of beauty, call the C and expect an exception to be raised.
Run the unit test:
C:\Projects\757\try2>\cygwin\bin\ruby.exe test_try.rb Loaded suite test_try Started doing it .. Finished in 0.0 seconds. 2 tests, 2 assertions, 0 failures, 0 errors
Sweet.
In summary, taking out swig makes writing try.c more tricky but overall it clears up the picture for me.
The book programming Ruby has a whole chapter on writing Ruby Extensions.

