c ruby ruby-c-extension

Extensión de ruby en C: ¿cómo especificar valores de argumento predeterminados para que funcionen?



ruby-c-extension (2)

Estoy tratando de escribir una extensión en C para ruby ​​que genere una clase. Estoy buscando cómo definir algunos argumentos predeterminados para una clase. Por ejemplo, si tengo este decleration de clase en ruby:

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

Puede inicializarlo con mc = MyClass.new("blah") , y el parámetro age 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é en verificar el valor de age contra Qnil o usar if ( TYPE(age) == T_UNDEF ) , pero solo obtengo segfaults de allí. Leer a través de README.EXT me lleva a creer que puedo lograr esto a través de rb_define_method utilizando el valor de argc , pero esto no fue demasiado claro. ¿Algunas ideas? Gracias.


Necesitas usar el argc de rb_define_method . Debes pasar -1 como argc a rb_define_method y usar rb_scan_args para manejar argumentos opcionales. Por ejemplo, el ejemplo de matt podría simplificarse a lo 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 del estante pragmático :

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 indicador que se usa para los argumentos con nombre y el hash de opciones. Sin embargo, todavía tengo que averiguar cómo funciona.

¿Por qué?

Hay muchas ventajas de usar rb_scan_args :

  1. Maneja los argumentos opcionales asignándolos a nil ( Qnil en C). Esto tiene el efecto secundario de prevenir un comportamiento extraño de su extensión si alguien pasa nil a uno de los argumentos opcionales, lo que sucede.
  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 rb_scan_args mayor detalle aquí: http://www.oreillynet.com/ruby/blog/2007/04/c_extension_authors_use_rb_sca_1.html


Tienes razón: puedes hacerlo utilizando rb_define_method y un valor negativo para argc .

Normalmente, argc especifica el número de argumentos que acepta su método, pero el uso de un valor negativo especifica que el método acepta un número 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 de 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 receptor, es decir, self . Luego puede manipular esta matriz, ya que necesita imitar los argumentos predeterminados o lo que sea que necesite, en su caso, podría verse 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 que se pase una matriz de Ruby a su método, que usted especifica utilizando -2 en su llamada a rb_define_method . En este caso, su método debe tener una firma como la VALUE func(VALUE obj, VALUE args) , donde obj es el objeto receptor ( self ), y args es una matriz Ruby que contiene los argumentos. En tu 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; }