It’s definitely worth learning the ruby API itself, first. This is from
the Pickaxe chapter on extending ruby:
···
void rb_define_attr(VALUE variable, const char *name, int read, int write)
Creates accessor methods for the given variable, with the given name. If
read is nonzero, create a read method; if write is nonzero, create a
write method.
This is apparently a typo, since class.c has:
rb_define_attr(klass, name, read, write)
VALUE klass;
const char *name;
int read, write;
So the first argument should be the class you are defining the attr in.
This should be called in your Init_module method
If you want to access data faster from C, you may want to define your
own DATA class and store your variable in the C data structure part of
the object, rather than in ruby ivars. (See README.EXT in the ruby
distribution for details.) That saves you the time it takes to do lookup
in the ivar table. OTOH, if you go this way you have to make sure the
garbage collector knows about any ruby objects stored in the data
struct–you have to define mark() and free() functions.
SWIG can help automate the process of wrapping classes and defining
accessors, which is especially nice if you have a huge library to wrap.
If you are starting from scratch to develop an extension, you might want
to look at my cgenerator library, which takes a different, more dynamic,
approach than SWIG:
http://raa.ruby-lang.org/list.rhtml?name=cgenerator
Using cgen, you can “include CShadow” in one of your classes, and define
attributes that are stored in a C data structure rather than as ivars.
The memory management stuff is handled automatically. Inheritance is
also handled transparently. Just call #commit on your class, and it
compiles and loads the library.
Here’s an example based on the code you wrote:
require ‘cgen/cshadow’
class MyClass
include CShadow
shadow_attr_accessor :my_c_string => “char *my_string”
shadow_attr_accessor :my_ruby_string => String
shadow_attr_accessor :my_ruby_object => Object
def initialize
# Must use 'self.varname = value', or else ruby will assign to
# local var
self.my_c_string = "default string"
# stored as null-terminated char *
self.my_ruby_string = "default string"
# stored as ruby VALUE string, after checking type is String
self.my_ruby_object = "default string"
# stored as ruby VALUE string, no type checking
end
def inspect
"<#{self.class}: %p, %p, %p>" %
[my_c_string, my_ruby_string, my_ruby_object]
end
# easy way to add a method written in C
define_c_method :foo do
c_array_args { # interface to rb_scan_args
required :x
optional :y
rest :stuff
block :block
typecheck :x => Numeric
default :y => "INT2NUM(NUM2INT(x) + 1)"
}
declare :result => "VALUE result"
body %{
result = rb_funcall(block, #{declare_symbol :call}, 4,
shadow->my_ruby_string,
x, y, stuff);
}
# note use of shadow var to access shadow attrs
returns "result"
end
end
MyClass.commit # write C lib, compile, load
my_obj = MyClass.new
p my_obj
==> <MyClass: “default string”, “default string”, “default string”>
block = proc do |str, x, y, stuff|
“%s, %p, %p, %p” % [str, x, y, stuff]
end
p my_obj.foo(1, &block)
==> “default string, 1, 2, ”
p my_obj.foo(5, 0, “stuff”, “more stuff”, &block)
==> “default string, 5, 0, ["stuff", "more stuff"]”