2011-10-02 14 views
16

Estoy tratando de escribir una extensión C para ruby ​​que generará una clase. Estoy buscando cómo definir algunos argumentos predeterminados para una clase. Por ejemplo, si tengo esta clase decleration en rubí:Ruby de extensión en C: cómo especificar valores de argumento predeterminados para que funcionen?

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

puede inicializar con mc = MyClass.new("blah"), y el parámetro de edad se establecerá internamente. ¿Cómo hago esto en C? Hasta ahora Tengo esto, pero esto obliga a entrar en el otro argumento:

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

pensé acerca de la comprobación del valor de age contra Qnil o el uso de if (TYPE(age) == T_UNDEF), pero apenas consigo segfaults desde allí. Leyendo a través de README.EXT me lleva a creer que puedo lograr esto a través de rb_define_method usando el valor de argc, pero esto no estaba muy claro. ¿Algunas ideas? Gracias.

Respuesta

29

Tiene razón; puede hacerlo usando rb_define_method y un valor negativo para argc.

Normalmente argc especifica el número de argumentos que acepta su método, pero al usar un valor negativo especifica que el método acepta una cantidad variable de argumentos, que Ruby pasará como una matriz.

Hay dos posibilidades. Primero, use -1 si desea que los argumentos pasen a su método en una matriz C. Su método tendrá una firma como VALUE func(int argc, VALUE *argv, VALUE obj) donde argc es el número de argumentos, argv es un puntero a los argumentos en sí, y obj es el objeto de recepción, es decir, self. A continuación, puede manipular esta matriz como sea necesario para imitar los argumentos por defecto o lo que usted necesita, en su caso, podría ser algo como esto:

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

La alternativa es tener una variedad de Ruby pasado a su método, que se especifique utilizando -2 en su llamada al rb_define_method. En este caso, su método debe tener una firma como VALUE func(VALUE obj, VALUE args), donde obj es el objeto de recepción (self) y args es una matriz de Ruby que contiene los argumentos. En su caso esto podría ser algo como esto:

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

gran escritura, me había Upvote dos veces si pudiera - gracias! – sa125

8

Sí es necesario utilizar el argc de rb_define_method. Debe pasar -1 como argc a rb_define_method y usar rb_scan_args para manejar argumentos opcionales. Por ejemplo, el ejemplo de mate se podría simplificar a la siguiente:

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

Uso

Derivado de la 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. 

Ejemplo:

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

Además, en Ruby 2, también hay un : marca que se utiliza para los argumentos con nombre y el hash de opciones. Sin embargo, todavía tengo que descubrir cómo funciona.

¿Por qué?

Hay muchas ventajas de utilizar rb_scan_args:

  1. Maneja argumentos opcionales asignándoles nil (Qnil en C). Esto tiene el efecto secundario de evitar el comportamiento extraño de su extensión si alguien pasa el nil a uno de los argumentos opcionales, lo que ocurre .
  2. Utiliza rb_error_arity para generar un ArgumentError en el formato estándar (por ejemplo, wrong number of arguments (2 for 1)).
  3. Por lo general, es más corto.

Las ventajas de rb_scan_args se dan más aquí: http://www.oreillynet.com/ruby/blog/2007/04/c_extension_authors_use_rb_sca_1.html

Cuestiones relacionadas