简体   繁体   中英

How do I extend my ruby class with a c extension?

If I have Foo::Bar written in Ruby, and I want to add a method to Bar as a C extension. Right now when I create Foo::Bar in C like this:

static VALUE Foo;
static VALUE Bar;

static VALUE 
print_string(VALUE self, VALUE string) {
  printf("%s", StringValuePtr(string));
  return Qnil;
}

void Init_foo() {
    Foo = rb_define_module("Foo");
    Bar = rb_define_class_under(Foo, "Bar", rb_cObject);
    rb_define_method(Bar, "print_string", print_string, 1);
}

But the problem is:

ruby-1.9.2-p180 :001 > require 'ext/foo'   #=> ["Foo"]
ruby-1.9.2-p180 :002 > f = Foo::Bar.new   #=> #<Foo::Bar:0x000001046bce48>
ruby-1.9.2-p180 :003 > f.original_ruby_method
NoMethodError: undefined method `original_ruby_method' for #<Foo::Bar:0x000001046bce48>

So I am essentially overwriting the original Foo::Bar. How do I extend it and not overwrite it?

I figured out a way to solve this issue.

void Init_foo() {
    rb_eval_string("require './lib/foo'");
    VALUE Bar = rb_path2class("Foo::Bar");
    rb_define_method(Bar, "print_string", print_string, 1);
}

An alternative to your solution, where you require your Ruby code from your C extension, would be to require the extension from the Ruby code.

Add require 'foo.so' or require 'ext/foo.so' (depending on where your compiled library ends up) to lib/foo.rb , then in the client code just call require 'foo' as normal (assuming lib is on your load path).

I think doing it this way round is clearer and more common.

Note that you can use the .so suffix even if you're platform produces something else, ie it'll still work on a mac when the actual file is a .bundle .

A function a bit hidden away - I had to dig into the source to find it, but you can use rb_const_get to get references to existing modules and classes.

void Init_foo() {
  Foo = rb_const_get( rb_cObject, rb_intern("Foo");
  Bar = rb_const_get( Foo, rb_intern("Bar");
  rb_define_method(Bar, "print_string", print_string, 1);
}

If you want to ensure the class/module is created if it doesn't exist:

void Init_foo() {
  if ( rb_const_defined( rb_cObject, rb_intern("Foo") ) )
    Foo = rb_const_get( rb_cObject, rb_intern("Foo");
  else
    Foo = rb_define_module("Foo");

  if ( rb_const_defined( Foo, rb_intern("Bar") ) )
    Bar = rb_const_get( Foo, rb_intern("Bar");
  else
    Bar = rb_define_class_under(Foo, "Bar", rb_cObject);

  rb_define_method(Bar, "print_string", print_string, 1);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM