17

我正在尝试为 ruby​​ 编写一个 C 扩展来生成一个类。我正在研究如何为类定义一些默认参数。例如,如果我在 ruby​​ 中有此类声明:

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);
}

我考虑过检查ageagainstQnil或 using的值if ( TYPE(age) == T_UNDEF ),但我只是从那里得到段错误。通读README.EXT让我相信我可以通过rb_define_method使用 的值来实现这一点argc,但这并不太清楚。有任何想法吗?谢谢。

4

2 回答 2

31

你是对的 - 你可以使用rb_define_method和 的负值来做到这一点argc

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

有两种可能性。首先,-1如果您希望将参数传递给 C 数组中的方法,请使用。您的方法将有一个签名,例如参数的数量VALUE func(int argc, VALUE *argv, VALUE obj)在哪里,是指向参数本身的指针,而 obj 是接收对象,即. 然后,您可以根据需要模拟默认参数或您需要的任何内容来操作此数组,在您的情况下,它可能看起来像这样:argcargvself

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 数组传递到您的方法中,您可以-2在调用rb_define_method. 在这种情况下,您的方法应该有一个类似的签名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;
}
于 2011-10-02T15:29:58.853 回答
8

您确实需要使用argcof rb_define_method。您应该-1作为argctorb_define_method和 userb_scan_args来处理可选参数。例如,matt 的示例可以简化为以下内容:

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;
}

用法

源自实用书架

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. 它通过分配可选参数nilQnil在 C 中)来处理可选参数。nil如果有人传递给可选参数之一,这具有防止您的扩展出现奇怪行为的副作用,这确实发生了。
  2. 它用于以标准格式(例如)rb_error_arity引发ArgumentError 。wrong number of arguments (2 for 1)
  3. 它通常更短。

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

于 2013-11-24T16:29:31.183 回答