This is part 5 of my exploits in Ruby C extension land.
If you haven't read the previous parts, you might want to go back there.
Heavy Work
To pay off for the horse, I was forced to work in the fields for two months. Heavy work, that is.
So will you. So better get your C manual.
The Implementation
Writing C for Ruby is pretty straight forward. You don't have to worry about interfacing with Ruby one bit when you write the implementation Code. That can be fitted in in 5 minutes after the actual C Code is working.
Ruby in C
But you can of course use the Ruby interface anytime in your Code to make your life easier -- be it when manipulating Arrays, storing Strings etc.
The pickaxe has a solid chapter on all sorts of useful stuff.
There also was a good post somewhere that told people to not write C but rather "Ruby in C". Unfortunately I've lost the link to that.
Nonetheless a good advice if you want to develop faster. Performance optimizations can be done later on anyways.
You can even use the rb_eval
and rb_funcall
to evaluate arbitrary Ruby code or call a function.
Interfacing
The only part where you really need to use the Ruby API is when you want your C code to be accessed by Ruby code (which at some point you will have to).
But fortunately, this is the really easy part. All you need to do is
- Declare an initialization function
- Declare one or more classes or modules
- Wrap some C structs in those classes and modules
- Declare one or more functions
- Associate a real C function with the declared Ruby functions
All of those things are done with a single line of code each.
Isn't that nice?
Initialization
For each extension, there must be an initialization function. The function follows a naming convention:
void Init_<extension name>(void)
So, our cranberry
extension would have to have
a void Init_cranberry(void)
function in one
of the C files.
It doesn't matter where, mkmf will find it.
Classes and Modules
Here's how you declare a new class/module:
VALUE class_Cranberry;
Then you have to make it accessible to Ruby in your init function:
class_Cranberry = rb_define_class("Cranberry", rb_cObject);
This tells Ruby that class_Cranberry
contains
the data of the Ruby class Cranberry
, which inherits
Object
. (For a list of the C names of standard Ruby
classes, see the ruby.h
header file.)
Wrapping C Structs
You can store the data contained in a C struct inside a Ruby object. Ruby then takes care of the lifecycle of that struct.
Imagine you have a struct that represents the Cranberry class in C, then to wrap a Cranberry object around it, call
VALUE new_object = Data_Wrap_Struct(class_Cranberry, NULL, free_Cranberry, some_struct);
This is typically done in allocator functions, e.g. new
.
free_Cranberry
points to a function of type
void free_Cranberry(void*)
, which Ruby will
call when your C object must be garbage collected.
That function will get a pointer to the wrapped struct
so it can deallocate its memory.
By the way, if you want to get the struct that resides under a Ruby object, use
StructType * some_struct;
Data_Get_Struct(ruby_object, StructType, some_struct);
That will change the some_struct
pointer to point
to your wrapped struct.
Declaring Functions
The last part of the puzzle is function declaration.
rb_define_singleton_method(class_Cranberry, "new", class_method_new, -1);
rb_define_method(class_Cranberry, "juice", method_juice, 1);
This tells Ruby to call class_method_new
whenever
Cranberry.new
is requested, and method_juice
whenever some_cranberry.juice
is called.
The -1
and 1
are the arities that Ruby uses
internally to decide if there are enough arguments
and what arguments to pass to your function.
NOTE: These arities work different than the arities
you get from Method#arity
!
A positive number means, your function will take just that many arguments, none less, none more.
A negative number -n
means, your function needs
at least n
arguments, but can take more.
This is used for both, the splash arguments
(def foo(*args)
), as well as optional arguments
(def foo(x = 12)
).
-1
means the function takes a variable number of
arguments, passed as a C array.
-2
means the function takes a variable number of
arguments, passed as a Ruby array.
In the next chapter I'll explain how that works in detail. So go and figure this out for your code, while I write that chapter.