One of the joys of Ruby is that you can write Ruby programs almost
directly in C. That is, you can use the same methods and the same
logic, but with slightly different syntax to accommodate C. For
instance, here is a small, fairly boring test class written in Ruby.
class Test
def initialize
@arr = Array.new
end
def add(anObject)
@arr.push(anObject)
end
end
|
The equivalent code in C should look somewhat familiar.
#include "ruby.h"
static VALUE t_init(VALUE self)
{
VALUE arr;
arr = rb_ary_new();
rb_iv_set(self, "@arr", arr);
return self;
}
static VALUE t_add(VALUE self, VALUE anObject)
{
VALUE arr;
arr = rb_iv_get(self, "@arr");
rb_ary_push(arr, anObject);
return arr;
}
VALUE cTest;
void Init_Test() {
cTest = rb_define_class("Test", rb_cObject);
rb_define_method(cTest, "initialize", t_init, 0);
rb_define_method(cTest, "add", t_add, 1);
}
|
Let's go through this example in detail, as it illustrates many of the
important concepts in this chapter. First off, we need to include the
header file ``
ruby.h
'' to obtain the necessary definitions.
Now look at the last function,
Init_Test
.
Every class or module
defines a C global function named
Init_
Name. This
function will be called when the interpreter first loads the extension
Name (or on startup for statically linked extensions). It is
used to initialize the extension and to insinuate it into the Ruby
environment. In this case, we define a new class named
Test
,
which is a subclass of
Object
(represented by the external symbol
rb_cObject
; see ``
ruby.h
'' for others).
Next we set up
add
and
initialize
as two instance methods
for class
Test
.
The calls to
rb_define_method
establish
a binding between the Ruby method name and the C function that will
implement it, so a call to the
add
method in Ruby will call the
C function
t_add
with one argument.
Similarly, when
new
is called for this class, Ruby will construct
a basic object and then call
initialize
, which we have defined
here to call the C function
t_init
with no (Ruby) arguments.
Now go back and look at the definition of
initialize
. Even
though we said it took no arguments, there's a parameter here! In
addition to any Ruby arguments, every method is passed an initial
VALUE
argument that contains the receiver for this method (the
equivalent of
self
in Ruby code).
The first thing we'll do in
initialize
is create a Ruby array
and set the instance variable
@arr
to point to it. Just as you
would expect if you were writing Ruby source, referencing an instance
variable that doesn't exist creates it.
Finally, the function
t_add
gets the instance variable
@arr
from the current object and calls
Array#push
to push the passed value
onto that array. When accessing instance variables in this way, the
@
-prefix is mandatory---otherwise the variable is created, but
cannot be referenced from Ruby.
Despite the extra, clunky syntax that C imposes, you're still writing
in Ruby---you can manipulate objects using all of the method calls
you've come to know and love, with the added advantage of being able
to craft tight, fast code when needed.
WARNING: Every C function that is callable from Ruby
must return a
VALUE
, even if it's just
Qnil
.
Otherwise, a core dump (or GPF) will be the likely result.
We can use the C version of the code in Ruby simply
by
require
-ing it dynamically at runtime (on
most platforms).
require "code/ext/Test"
t = Test.new
t.add("Bill Chase")
|
C/Ruby datatype conversion functions and
macros
C Datatypes to Ruby Objects:
|
|
INT2NUM(int) |
-> Fixnum or Bignum
|
|
INT2FIX(int) |
-> Fixnum (faster) |
|
INT2NUM(long or int) |
-> Fixnum or Bignum
|
|
INT2FIX(long or int) |
-> Fixnum (faster) |
|
CHR2FIX(char) |
-> Fixnum
|
|
rb_str_new2(char *) |
-> String
|
|
rb_float_new(double) |
-> Float
|
|
Ruby Objects to C Datatypes:
|
int |
NUM2INT(Numeric) |
(Includes type check) |
int |
FIX2INT(Fixnum) |
(Faster) |
unsigned int |
NUM2UINT(Numeric) |
(Includes type check) |
unsigned int |
FIX2UINT(Fixnum) |
(Includes type check) |
long |
NUM2LONG(Numeric) |
(Includes type check) |
long |
FIX2LONG(Fixnum) |
(Faster) |
unsigned long |
NUM2ULONG(Numeric) |
(Includes type check) |
char |
NUM2CHR(Numeric or String) |
(Includes type check) |
char * |
STR2CSTR(String) |
|
char * |
rb_str2cstr(String, int *length) |
Returns length as well |
double |
NUM2DBL(Numeric) |
|
|
|