Peter's Blog

Redefining the Impossible

Testing C with Ruby


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.


Filed under: notswig ruby

Comments are Closed