Jared's Weblog

Main Page - Books - Links - Resources - About - Contact Me - RSS


Jared Richardson Most Popular
Help! I've Inherited Legacy Code
Testing Untestable Code
Continuous Integration... Why Bother?
Mock Client Testing
The Art of Work
Technical Idiot Savants
Targetted Skills Aquition vs Free Range Chickens
The Habits of Highly Effective Developers



Blog Archive
2005-December
2005-November
2005-October
2005-September
2005-August
2005-July
2005-June

Sat, 25 Mar 2006

Blending C and Ruby

A project that I've been working on has involved calling C code from Ruby code. It was much easier to do than I anticipated and I thought others might find it interesting.

Why would you do this? Suppose you have a bit of Ruby code that's too slow in a key portion of your program? You can easily move that bit of code out to C. This gets all the speed of execution in C where you need it combined with the speed of program creation that Ruby provides.

This would also be an easy way to wrap your legacy C code, access hardware with a C interface, etc. The possibilities are literally endless.

So how do you do this? First, let me tell you that these examples are on Linux, but they are pretty simple and I don't think you'll have much trouble duplicating them on Windows. I just don't have a C compiler installed on my Windows box at the moment. If enough people ask for it, I'll set up the environment and run this to Windows, but I don't see how an example this simple wouldn't translate directly.

Another caveat, I'm skimming this topic very lightly. I'm not explaining the "why" behind most of these directions. If you want to go deeper, check out the pickaxe book, avaiable in both the second edition (new, improved, and up to date) or the original first edition (online and free). The pick axe book has an entire chapter on extending Ruby with C.

You need three files to do this work. The first is your C code file, the second is your Ruby code file, and the last is a configuration file called "extconf.rb". Extconf.rb is used by a Ruby utility called mkmf (makes your Makefile). It creates your rather complicated build file for this project.

Let's start with the Ruby file. Here it is.

#include "ruby.h"
#include 

static VALUE cSampleC;

static VALUE exec(VALUE self) {
        printf("\n\nExec, written in C, was run from within the Ruby code \n\n");
        return Qnil;
}

void Init_SampleC() {
        cSampleC = rb_define_class("SampleC", rb_cObject);
        rb_define_method(cSampleC, "exec", exec, 0);
}

What do we have here? First, you must include ruby.h to make your C code ruby aware.

Next, you need a static variable to hold your object reference. That's cSampleC.

Your C routine has to return the type VALUE. This is equivalent to a Ruby Object. You need to have (at least) a single argument VALUE self. You can have more, but this one is required.

Finally, return Qnil if you really want a void type.

The last block, init_SampleC declares your C objects so that Ruby can see them. This code maps your C to Ruby.

rb_define_class creates a Ruby object for you. In my example, it's "SampleC". NOTE:This object must start with an upper case letter. I worked with "ingres" for nearly two hours before I tried "Ingres" and everything just worked. :( The second argument in this method is your static VALUE object that holds the referenc to this object.

Finally, create a mapping from your routine to your Ruby object. rb_define_method does this for you. The arguments? First, the static VALUE object again, then your Ruby object name, your C method name, and finally, the number of arguments you're passing in (don't count "self").

Okay, we're done with the C file. Let's create a Makefile.

Here's my extconf.rb file:

require 'mkmf' create_makefile("SampleC")

Complicated, but I think you can handle it. :) This file can get a lot more complicated, but this one does all we need.

To invoke this, type:

ruby -r mkmf extconf.rb

If all goes well, you'll see

creating Makefile

Now you can type make, then make install.

At this point, we should have a Ruby object we can run!

The Ruby code is pretty straightforward as well.

require "SampleC"
 i=SampleC.new
 i.exec

Of course, you can run this from within irb as well.

That's it. You can now run ruby testMyCode.rb and see

Exec, written in C, was run from within the Ruby code

I'm using this feature to include some database libraries. Let me know if that interests anyone. If it does, I'll write that one up. It's a bit more complicated (and not finished yet!), but if there's some interest, I'll get it posted in a few weeks.

Enjoy!

Jared

posted at: 14:37 | path: | permanent link to this entry