Inheritance of Ruby classes defined in C

Inheritance can be annoying
Added by Christoph Kappel 11 months ago

Creating a Ruby class directly via C is really painless, under normal circumstances you don't even have to worry about the alloc stuff. Just supply a #initialize when needed and you are done.

Numbers: on /off1 klass = rb_define_class("Test", rb_cObject);
2 rb_define_method(klass, "initialize", TestInit, 0);

Inheritance in this case works like a charm, your initialize will be called like expected. It get's more tricky when you need to call Data_Wrap_Struct. The function itself allocates an object and returns it with your supplied data pointer attached.

First solution you will most likely come up with is defining your own .new like this:

Numbers: on /off1 klass = rb_define_class("Test", rb_cObject);
2 rb_define_singleton_method(klass, "new", TestNew, -1);

Since we overwrite the default .new we need to do the call to #initialize by ourself:

Numbers: on /off 1 VALUE
 2 TextNew(int argc,
 3   VALUE *argv,
 4   VALUE self)
 5 {
 6   VALUE instance = Qnil;
 7   void *ptr = NULL;
 9   /* Fill ptr with something reasonable */
11   instance = Data_Wrap_Struct(self, NULL, NULL, ptr);
13   /* Pass arguments to initialize */
14   rb_obj_call_init(instance, argc, argv);
16   return instance;
17 }

This will wrap our data pointer and call #initialize with all arguments - no problems so far.

Problems start when someone wants to inherit from this class:

Numbers: on /off1 class AnotherTest < Test
2   :attr_reader = arg3
4   def initialize(arg1, arg2, arg3)
5     super(arg1, arg2)
6     @var = arg3
7   end
8 end

Now rb_obj_call_init calls the default #initialize and not the one from the inherited class - this will most likely end with an ArgumentError.

Remember that the first version just worked and Data_Wrap_Struct creates a new object - so we can't call it in #initialize where self is immutable.

The solution is easy, don't create your own .new. We can use allocate to just allocate our object, let Ruby do the dirty stuff and rely on it to call our #initialize.

In C it looks like this:

Numbers: on /off 1 klass = rb_define_class("Test", rb_cObject);
 3 /* This defines our alloc function, slightly changed syntax */
 4 rb_define_alloc_func(klass, TestAlloc);
 6 rb_define_method(klass, "initialize", TestInit, 0);
 9 TestAlloc(VALUE self)
10 {
11   VALUE instance = Qnil;
12   void *ptr = NULL;
14   /* Fill ptr with something reasonable */
16   instance = Data_Wrap_Struct(self, NULL, NULL, ptr);
18   return instance;
19 }
22 TestInit(VALUE self)
23 {
24   /* Do initialization here */
26   return Qnil;
27 }