Writing C Extensions, Improved

| | Comments (9)

I recently read How to create a Ruby extension in C in under 5 minutes by Peter Cooper. I decided to replicate the example to see how things fare these days. I basically followed his lead, typing in everything, but cleaning up the code to suit my tastes a bit better (and in some cases, to be more correct in C-land).

First, I wrote the following:

#!/usr/local/bin/ruby -w

require 'mkmf'
extension_name = 'example'
dir_config(extension_name)
create_makefile(extension_name)

Nothing fancy, just a simple mkmf generator. I don't like mkmf (esp. its insides), but this is clean enough. After that, I wrote (and cleaned up) the C file:

#include "ruby.h"

static VALUE method_test1(VALUE self) {
  int x = 10;
  return INT2NUM(x);
}

void Init_example() {
  VALUE Example = rb_define_module("Example");
  rb_define_method(Example, "test1", method_test1, 0);
}

Nothing fancy. The only real differences from the original were made to keep the symbol table clean. All in all, it took me about 7 minutes, not too far off from the original article's title. And sure enough, it can be run just fine via:

./example-build.rb
make
ruby -I. -rexample -e 'include Example; puts test1'

Not too bad. I don't have any real complaints about that... except that the alternative is so much easier and so much cleaner that I don't really see why anyone would choose this way of going about it. Examine:

#!/usr/local/bin/ruby -w

require 'rubygems'
require 'inline'

class Example
  inline(:C) do |builder|
    builder.c "int test1() {
                 int x = 10;
                 return x;
               }"
  end
end

p Example.new.test1

and you run it with:

./example.rb

Notice how I just run the script? That is because compiling, linking, and loading are all handled for you, as needed, and you can never accidentally skip a step. Notice how I just write plain C? int test1() is about as clean as you can get. Notice how I return x straight up? No type conversions here.

The point is to remove the distractions so you can get your work done. No more wondering "did I type make?" or "what is the proper macro to convert a string?" as it is all handled for you. Give developers the tools they need so they can stay in thought, not in process.

It doesn't get simpler than this... and really, it shouldn't ever be more difficult than this.

9 Comments

I agree the latter is nicer, but I have one problem with it: it's harder to port to other non-C-based Ruby implementations.

In the former, you defer to the underlying implementation to locate and load the native library in question, allowing it to do whatever it wants to do to make those native methods available to you. In the latter, you're explicitly tying it to C and expecting that will always be acceptable...when in many cases (no compiler present, compiler not configured correctly, Java-based impl) it will not be.

Now of course, once we port RubyInline to work with JRuby, something like the following will be possible (formatting unfortunately lost):

class Example; inline do |builder|; builder.c "int test1() { int x = 10; return x; }"; builder.java "public int test1() {; int x = 10; return x; }"; end end

Ignoring for the moment the fact that the C and Java code are almost identical, this would allow RubyInline to work correctly under JRuby, generating the native version of the code transparently. Of course then you need a CLR version and a Parrot version :)

Charles, (god I hope this formats correctly)

How is it harder to port to other non-C-based ruby implementations? Granted, I don't use jruby, so I'm not sure what your expected extension path is. Assuming it is JNI, I don't see that being much of a stretch to make work at all.

"In the former, you defer to the underlying implementation to locate and load the native library in question"

as does ruby inline... We get all our configuration information from rbconfig.rb, which is prettymuch_ what mkmf does.

"In the latter, you're explicitly tying it to C and expecting that will always be acceptable"

not true. inline is language agnostic, it just defaults to C. Teaching inline how to do JNI-style C (or whatever jruby prefers) is not hard.

"when in many cases (no compiler present, compiler not configured correctly, Java-based impl) it will not be."

Also not true... we've had a binary packager in inline for over a year, so compiler not present isn't an issue. Compiler not configured correctly either implies the absence of the use of the binary packager, or implies that ruby itself wasn't compiled correctly (and presumably, makes the rest of the issue moot)--either way, you're fucked but not because of inline.

To drive my point home a bit more, your example is more correct like:

class Example inline(:C) { |builder| builder.c "int test1() { int x = 10; return x; }" } inline(:Java) { |builder| builder.java "public int test2() { int x = 10; return x; }" } end

That example looks about like what we'd want. JRuby will prefer Java, so we will look at adding Java capability to RubyInline. What a world it shall be!!

How about poor suckers like me that lives on a Windows platform with no build environment installed? I (we) usually depends on nice people providing us with a pre-built binary whenever there is a Ruby library that uses native extensions.

Won't we lose our precious life line (pre-built binaries) if people starts using RubyInline?

Marcus,

As I said above: "we've had a binary packager in inline for over a year, so compiler not present isn't an issue."

Ok. Sorry about that. Apparently didn't read the comments good enough. Sounds good though :)

While I can see your point for small little chunks of C like this, I'm not sure I see how Inline would work for larger, more-complex APIs. Especially ones where the functions presented in the C API don't map very well onto the Ruby idiom, and you have to do a lot of hand-holding behind the scenes to map structs and procedural functions into objects and methods.

Do you have an example of using Inline to wrap a C library on the order of libxml or SQLite? I'm currently writing a binding for the Abiword variant of the CMU Link Grammar library, and I'm interested to see if it could make things easier.

I'm also interested in examples of wrapping external C libraries. I'm writing a wrapper for the Spidermonkey library.

I've written wrappers for two libraries: Intersystems Cache database driver and SGI Irix WireTap. My experience tell me, that using common way is more convenient because of many logic in C and smart type translations.

I don't want to pass two integers and three strings into function, I want to pass smart object.

Leave a comment