2011-10-02 68 views
16

我想写一个C扩展到Ruby将会生成一个类。我正在研究如何为某个类定义一些默认参数。举例来说,如果我有红宝石该类decleration:扩展在C中的红宝石 - 如何指定默认参数值功能?

class MyClass 
    def initialize(name, age=10) 
    @name = name 
    @age = age 
    end 
end 

你可以用mc = MyClass.new("blah")初始化,年龄参数将被内部设置。我如何在C中做到这一点?到目前为止,我得到了这一点,但是这股势力进入另一种说法:

require "ruby.h" 

static VALUE my_init(VALUE self, VALUE name, VALUE age) 
{ 
    rb_iv_set(self, "@name", name); 
    rb_iv_set(self, "@age", age); 

    return self; 
} 

VALUE cMyClass; 

void Init_MyClass() 
{ 
    // create a ruby class instance 
    cMyClass = rb_define_class("MyClass", rb_cObject); 

    // connect the instance methods to the object 
    rb_define_method(cMyClass, "initialize", my_init, 2); 
} 

我想到了检查,对Qnilage值或使用if (TYPE(age) == T_UNDEF),但我刚刚从那里得到段错误。通过README.EXT的阅读导致我相信我可以通过rb_define_method使用argc的值完成此操作,但这并不太清楚。有任何想法吗?谢谢。

回答

29

你是对的 - 你可以使用rb_define_method和负值argc

通常,argc指定您的方法接受的参数数量,但使用负值指定该方法接受可变数量的参数,该参数将作为数组传入。

有两种可能性。首先,如果您希望在C数组中传递给您的方法的参数,请使用-1。您的方法将具有如VALUE func(int argc, VALUE *argv, VALUE obj)这样的签名,其中argc是参数的数量,argv是指向参数本身的指针,而obj是接收对象,即self。然后,您可以根据需要模拟默认参数操纵这个数组或任何你需要的,你的情况可能是这个样子:

static VALUE my_init(int argc, VALUE* argv, VALUE self) { 

    VALUE age; 

    if (argc > 2 || argc == 0) { // there should only be 1 or 2 arguments 
     rb_raise(rb_eArgError, "wrong number of arguments"); 
    } 

    rb_iv_set(self, "@name", argv[0]); 

    if (argc == 2) {  // if age has been included in the call... 
     age = argv[1];  // then use the value passed in... 
    } else {    // otherwise... 
     age = INT2NUM(10); // use the default value 
    } 

    rb_iv_set(self, "@age", age); 

    return self; 
} 

另一种方法是有一个Ruby数组传递给你的方法,你在致电rb_define_method时,请使用-2进行指定。在这种情况下,您的方法应该有一个签名,如VALUE func(VALUE obj, VALUE args),其中obj是接收对象(self),而args是包含参数的Ruby数组。在你的情况,这可能是这个样子:

static VALUE my_init(VALUE self, VALUE args) { 

    VALUE age; 

    long len = RARRAY_LEN(args); 

    if (len > 2 || len == 0) { 
     rb_raise(rb_eArgError, "wrong number of arguments"); 
    } 

    rb_iv_set(self, "@name", rb_ary_entry(args, 0)); 

    if (len == 2) { 
     age = rb_ary_entry(args, 1); 
    } else { 
     age = INT2NUM(10); 
    } 

    rb_iv_set(self, "@age", age); 

    return self; 
} 
+0

伟大的写了起来,我想如果我能给予好评两次 - 谢谢! – sa125

8

你需要使用rb_define_methodargc。您应通过-1作为argcrb_define_method并使用rb_scan_args来处理可选参数。例如,亚光的例子可以简化为以下:

static VALUE my_init(int argc, VALUE* argv, VALUE self) { 

    VALUE name, age; 
    rb_scan_args(argc, argv, "11", &name, &age); // informs ruby that the method takes 1 mandatory and 1 optional argument, 
                // the values of which are stored in name and age. 

    if (NIL_P(age))   // if no age was given... 
     age = INT2NUM(10); // use the default value 

    rb_iv_set(self, "@age", age); 
    rb_iv_set(self, "@name", name); 

    return self; 
} 

用法

Pragmatic Bookshelf衍生:

int rb_scan_args (int argcount, VALUE *argv, char *fmt, ... 

Scans the argument list and assigns to variables similar to scanf: 

fmt A string containing zero, one, or two digits followed by some flag characters. 
     The first digit indicates the count of mandatory arguments; the second is the count of optional arguments. 
    A * means to pack the rest of the arguments into a Ruby array. 
    A & means that an attached code block will be taken and assigned to the given variable 
     (if no code block was given, Qnil will be assigned). 

After the fmt string, pointers to VALUE are given (as with scanf) to which the arguments are assigned. 

实施例:

VALUE name, one, two, rest; 
rb_scan_args(argc, argv, "12", &name, &one, &two); 
rb_scan_args(argc, argv, "1*", &name, &rest); 

此外,在Ruby 2,还有一个:用于命名参数和选项散列的标志。但是,我还没有弄清楚它是如何工作的。

为什么?

有使用rb_scan_args的许多优点:

  1. 它通过(在C Qnil)赋予它们nil处理可选参数。如果有人将nil传递给其中一个可选参数发生,则防止您的扩展中出现奇怪行为的副作用。
  2. 它使用rb_error_arity以标准格式(例如wrong number of arguments (2 for 1))增加ArgumentError
  3. 它通常较短。

rb_scan_args的优点在这里进一步阐述:http://www.oreillynet.com/ruby/blog/2007/04/c_extension_authors_use_rb_sca_1.html