2011-10-02 29 views
16

Tôi đang cố gắng viết phần mở rộng C thành ruby ​​để tạo một lớp. Tôi đang tìm cách xác định một số đối số mặc định cho một lớp. Ví dụ, nếu tôi có decleration lớp này trong ruby:Mở rộng ruby ​​trong C - cách xác định giá trị đối số mặc định để hoạt động?

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

Bạn có thể khởi tạo nó với mc = MyClass.new("blah"), và tham số tuổi sẽ được thiết lập trong nội bộ. Làm thế nào để làm điều này trong C? Cho đến nay tôi nhận được điều này, nhưng lực lượng này vào lập luận khá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); 
} 

Tôi nghĩ về việc kiểm tra giá trị của age chống Qnil hoặc sử dụng if (TYPE(age) == T_UNDEF), nhưng tôi chỉ nhận được segfaults từ đó. Đọc qua số README.EXT dẫn tôi tin rằng tôi có thể thực hiện điều này thông qua rb_define_method bằng cách sử dụng giá trị argc, nhưng điều này không quá rõ ràng. Bất kỳ ý tưởng? Cảm ơn.

Trả lời

29

Bạn nói đúng - bạn có thể thực hiện việc này bằng cách sử dụng rb_define_method và giá trị âm cho argc.

Thông thường argc chỉ định số lượng đối số mà phương thức của bạn chấp nhận, nhưng sử dụng giá trị âm xác định rằng phương thức chấp nhận số lượng đối số thay đổi, mà Ruby sẽ chuyển vào dưới dạng mảng.

Có hai khả năng. Đầu tiên, sử dụng -1 nếu bạn muốn các đối số được chuyển vào phương thức của bạn trong một mảng C. Phương thức của bạn sẽ có chữ ký như VALUE func(int argc, VALUE *argv, VALUE obj) trong đó argc là số lượng đối số, argv là con trỏ đến đối số và obj là đối tượng nhận, tức là self. Sau đó bạn có thể thao tác mảng này như bạn cần phải bắt chước đối số mặc định hoặc bất cứ điều gì bạn cần, trong trường hợp của bạn nó có thể trông giống như thế này:

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

Cách khác là để có một mảng của Ruby được truyền vào phương pháp của bạn, mà bạn chỉ định bằng cách sử dụng -2 trong cuộc gọi của bạn tới rb_define_method. Trong trường hợp này, phương thức của bạn phải có chữ ký như VALUE func(VALUE obj, VALUE args), trong đó obj là đối tượng nhận (self) và args là một mảng Ruby chứa các đối số. Trong trường hợp của bạn này có thể trông như thế này:

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

lớn ghi lên, tôi muốn upvote nó hai lần nếu tôi có thể - cảm ơn! – sa125

8

Bạn cần phải sử dụng argc của rb_define_method. Bạn nên vượt qua -1 làm argc đến rb_define_method và sử dụng rb_scan_args để xử lý các đối số tùy chọn. Ví dụ, ví dụ mờ của thể được đơn giản hóa như sau:

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

Cách sử dụng

Xuất phát từ 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. 

Ví dụ:

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

Hơn nữa, trong Ruby 2, cũng có một : cờ được sử dụng cho các đối số được đặt tên và băm tùy chọn. Tuy nhiên, tôi vẫn chưa tìm ra cách nó hoạt động.

Tại sao?

Có rất nhiều lợi thế của việc sử dụng rb_scan_args:

  1. Nó xử lý đối số tùy chọn bằng cách gán cho họ nil (Qnil trong C). Điều này có tác dụng phụ ngăn chặn hành vi kỳ quặc từ tiện ích mở rộng của bạn nếu ai đó vượt qua nil đến một trong các đối số tùy chọn, trong đó sẽ xảy ra.
  2. Sử dụng rb_error_arity để tăng ArgumentError ở định dạng chuẩn (ví dụ: wrong number of arguments (2 for 1)).
  3. Nó thường ngắn hơn.

Những lợi thế của rb_scan_args đang tiếp tục xây dựng ở đây: http://www.oreillynet.com/ruby/blog/2007/04/c_extension_authors_use_rb_sca_1.html

Các vấn đề liên quan