在 C 中扩展 ruby​​ - 如何为函数指定默认参数值?

发布于 2024-12-07 12:30:42 字数 942 浏览 0 评论 0原文

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

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

您可以使用 mc = MyClass.new("blah") 对其进行初始化,并且将在内部设置 Age 参数。我如何在 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);
}

我考虑根据 Qnil 检查 age 的值或使用 if ( TYPE(age) = = T_UNDEF ),但我只是从那里得到了段错误。通读 README.EXT 使我相信我可以使用 argc 的值通过 rb_define_method 来完成此操作,但这还不太清楚。有什么想法吗?谢谢。

I'm trying to write a C extension to ruby that'll generate a class. I'm looking on how to define some default arguments to a class. For example, if I have this class decleration in ruby:

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

You can initialize it with mc = MyClass.new("blah"), and the age parameter will be set internally. How do I do this in C? So far I got this, but this forces entering the other argument:

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

I thought about checking the value of age against Qnil or using if ( TYPE(age) == T_UNDEF ), but I just get segfaults from there. Reading through README.EXT leads me to believe I can accomplish this through rb_define_method using the value of argc, but this wasn't too clear. Any ideas? Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

土豪我们做朋友吧 2024-12-14 12:30:43

你是对的 - 您可以使用 rb_define_methodargc 的负值来做到这一点。

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

有两种可能性。首先,如果您希望参数以 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 数组传递到您的方法中,您可以使用 - 指定该数组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;
}

You're right - you can do this using rb_define_method and a negative value for argc.

Normally argc specifies the number of arguments your method accepts, but using a negative value specifies that the method accepts a variable number of arguments, which Ruby will pass in as an array.

There are two possibilities. First, use -1 if you want the arguments passed in to your method in a C array. Your method will have a signature like VALUE func(int argc, VALUE *argv, VALUE obj) where argc is the number of arguments, argv is a pointer to the arguments themselves, and obj is the receiving object, i.e. self. You can then manipulate this array as you need to mimic default arguments or whatever you need, in your case it might look something like this:

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

The alternative is to have a Ruby array passed into your method, which you specify by using -2 in your call to rb_define_method. In this case, your method should have a signature like VALUE func(VALUE obj, VALUE args), where obj is the receiving object (self), and args is a Ruby array containing the arguments. In your case this might look something like this:

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;
}
捎一片雪花 2024-12-14 12:30:43

您确实需要使用rb_define_methodargc。您应该将 -1 作为 argc 传递给 rb_define_method 并使用 rb_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;
}

用法

源自实用书架< /a>:

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. 它通过分配nil(C中的Qnil)来处理可选参数)。如果有人将 nil 传递给可选参数之一(这种情况确实发生),这会产生副作用,防止您的扩展出现奇怪的行为。
  2. 它使用 rb_error_arity 来引发 ArgumentError 采用标准格式(例如参数数量错误(2 表示1))。
  3. 它通常更短。

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

You do need to use the argc of rb_define_method. You should pass -1 as the argc to rb_define_method and use rb_scan_args to handle optional arguments. For example, matt's example could be simplified to the following:

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

Usage

Derived from the 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.

Example:

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

Furthermore, in Ruby 2, there is also a : flag that is used for named arguments and the options hash. However, I have yet to figure out how it works.

Why?

There are many advantages of using rb_scan_args:

  1. It handles optional arguments by assigning them nil (Qnil in C). This has the side effect of preventing odd behaviour from your extension if someone passes nil to one of the optional arguments, which does happen.
  2. It uses rb_error_arity to raise an ArgumentError in the standard format (ex. wrong number of arguments (2 for 1)).
  3. It's usually shorter.

The advantages of rb_scan_args are further elaborated here: http://www.oreillynet.com/ruby/blog/2007/04/c_extension_authors_use_rb_sca_1.html

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文