Ruby C extensiones API preguntas
ruby-c-extension (2)
Entonces, recientemente tuve la desafortunada necesidad de hacer una extensión en C para Ruby (debido al rendimiento). Como estaba teniendo problemas para entender el VALUE
(y aún lo hago), busqué en la fuente de Ruby y encontré: typedef unsigned long VALUE;
( Enlace a la Fuente , pero notará que hay algunas otras formas de hacerlo, pero creo que es esencialmente un long
; corríjame si me equivoco). Entonces, mientras investigaba esto, encontré una interesante entrada de blog , que dice:
"... en algunos casos, el objeto VALUE podría ser los datos en lugar de apuntar a los datos".
Lo que me confunde es que, cuando intento pasar una cadena a C desde Ruby, y uso RSTRING_PTR();
en el VALUE
(pasado a la función C de Ruby), e intente "depurarlo" con strlen();
devuelve 4. Siempre 4.
código de ejemplo:
VALUE test(VALUE inp) {
unsigned char* c = RSTRING_PTR(inp);
//return rb_str_new2(c); //this returns some random gibberish
return INT2FIX(strlen(c));
}
Este ejemplo devuelve siempre 1 como la longitud de la cadena:
VALUE test(VALUE inp) {
unsigned char* c = (unsigned char*) inp;
//return rb_str_new2(c); // Always "/x03" in Ruby.
return INT2FIX(strlen(c));
}
A veces, en Ruby, veo una excepción que dice "No se puede convertir el módulo a cadena" (o algo así, Sin embargo, estaba jugando con el código tanto tratando de resolver esto que ahora no puedo reproducir el error. el error sucedería cuando intentara StringValuePtr();
[No estoy muy claro qué hace exactamente esto. La documentación dice que cambia el parámetro pasado a char*
] en inp):
VALUE test(VALUE inp) {
StringValuePtr(inp);
return rb_str_new2((char*)inp); //Without the cast, I would get compiler warnings
}
Entonces, el código Ruby en cuestión es: MyMod::test("blahblablah")
EDITAR : Se corrigieron algunos errores tipográficos y se actualizó un poco la publicación.
Las preguntas
- ¿Qué es exactamente lo que tiene
VALUE imp
? ¿Un puntero al objeto / valor? El valor en sí? - Si mantiene el valor en sí mismo: ¿cuándo hace eso, y hay una manera de verificarlo?
- ¿Cómo accedo realmente al valor (ya que parece que accedo a casi todo menos el valor)?
PD: Mi comprensión de C no es realmente la mejor, pero es un trabajo en progreso; Además, lea los comentarios en los fragmentos de código para obtener una descripción adicional (si es que ayuda).
¡Gracias!
Ruby Strings vs. C Strings
Comencemos con las cuerdas primero. En primer lugar, antes de intentar recuperar una cadena en C, es un buen hábito llamar a StringValue(obj)
en su VALUE
primero. Esto asegura que realmente tratará con una cadena Ruby al final, porque si aún no es una cadena, la convertirá en una al forzarla con una llamada al método to_str
ese objeto. Por lo tanto, esto hace que las cosas sean más seguras y evita las fallas de seguridad ocasionales que podría obtener de otra manera.
Lo siguiente a tener en cuenta es que las cadenas Ruby no están terminadas en /0
ya que su código C esperaría que hicieran que cosas como strlen
etc. funcionen como se espera. Las cadenas de Ruby llevan la información de su longitud con ellos, por eso, además de RSTRING_PTR(str)
también está la RSTRING_LEN(str)
para determinar la longitud real.
Entonces, lo que hace ahora StringValuePtr
es devolverle el char *
que no está terminado en cero: esto es excelente para los búferes en los que tiene una longitud separada, pero no para lo que quiere, por ejemplo, strlen
. En su lugar, use StringValueCStr
, modificará la cadena para que termine en cero, de modo que sea seguro para el uso con funciones en C que esperan que termine en cero. Pero, intente evitar esto siempre que sea posible, porque esta modificación es mucho menos eficaz que recuperar la cadena que no termina en cero que no tiene que ser modificada en absoluto. Es sorprendente que vigile esto, ya que rara vez realmente necesitará cadenas en C "reales".
el yo como un argumento de VALOR implícito
Otra razón por la que su código actual no funciona como se esperaba es que todas las funciones de C a las que Ruby llamará se pasan a self
como un VALUE
implícito.
Ningún argumento en Ruby (por ejemplo, obj.doit) se traduce a
Valor doit (valor propio)
La cantidad fija de argumentos (> 0, por ejemplo, obj.doit (a, b)) se traduce a
VALOR doit (VALOR self, VALUE a, VALUE b)
Var args en Ruby (por ejemplo, obj.doit (a, b = nil)) se traduce en
VALOR doit (int argc, VALUE * argv, VALUE self)
en ruby Entonces, en lo que estabas trabajando en tu ejemplo no es la cadena que Ruby te pasó, sino el valor actual de self
, que es el objeto que era el receptor cuando llamaste a esa función. Una definición correcta para tu ejemplo sería
static VALUE test(VALUE self, VALUE input)
Lo hice static
para señalar otra regla que deberías seguir en tus extensiones C. Haga que sus funciones de C solo sean públicas si desea compartirlas entre varios archivos de origen. Dado que casi nunca es el caso de la función que adjunta a una clase de Ruby, debe declararlas como static
de forma predeterminada y solo hacerlas públicas si existe una buena razón para hacerlo.
¿Qué es VALOR y de dónde viene?
Ahora a la parte más difícil. Si profundiza en las rb_objnew internas de Ruby, encontrará la función rb_objnew en gc.c. Aquí puede ver que cualquier objeto Ruby recién creado se convierte en un VALUE
al ser lanzado como uno de algo que se llama lista freelist
. Se define como:
#define freelist objspace->heap.freelist
Puede imaginar el objspace
como un gran mapa que almacena todos y cada uno de los objetos que actualmente están vivos en un momento dado en su código. Aquí también es donde el recolector de basura cumple con su deber y la estructura de heap
en particular es el lugar donde nacen los nuevos objetos. La "lista libre" del montón se declara de nuevo como RVALUE *
. Esta es la representación interna en C de los tipos incorporados de Ruby. Un RVALUE
realidad se define de la siguiente manera:
typedef struct RVALUE {
union {
struct {
VALUE flags; /* always 0 for freed obj */
struct RVALUE *next;
} free;
struct RBasic basic;
struct RObject object;
struct RClass klass;
struct RFloat flonum;
struct RString string;
struct RArray array;
struct RRegexp regexp;
struct RHash hash;
struct RData data;
struct RTypedData typeddata;
struct RStruct rstruct;
struct RBignum bignum;
struct RFile file;
struct RNode node;
struct RMatch match;
struct RRational rational;
struct RComplex complex;
} as;
#ifdef GC_DEBUG
const char *file;
int line;
#endif
} RVALUE;
Es decir, básicamente una unión de tipos de datos centrales que Ruby conoce. ¿Echando de menos algo? Sí, los valores de Fixnums, Symbols, nil
y boolean no están incluidos allí. Esto se debe a que este tipo de objetos se representan directamente utilizando el unsigned long
que un VALUE
reduce al final. Creo que la decisión de diseño que hubo (además de ser una buena idea) es que la falta de referencia a un puntero puede ser un poco menos eficiente que los cambios de bits que se necesitan actualmente cuando se transforma el VALUE
a lo que realmente representa. Esencialmente
obj = (VALUE)freelist;
dice "dame los puntos de lista gratuitos que recibas actualmente y trátalos como si unsigned long
. Esto es seguro porque freelist es un puntero a un RVALUE
, y un puntero también puede interpretarse con seguridad como unsigned long
. Esto implica que cada VALUE
excepto los que llevan Fixnums, símbolos, nil o booleanos, son esencialmente punteros a un RVALUE
, los otros están representados directamente dentro del VALUE
.
Su última pregunta, ¿cómo puede verificar lo que significa un VALUE
? Puede usar la macro TYPE(x)
para verificar si el tipo de VALUE
sería uno de los "primitivos".
VALUE test(VALUE inp)
El primer problema está aquí: inp es self (entonces, en su caso, el módulo). Si desea referirse al primer argumento, debe agregar un argumento propio antes de eso (lo que me permite agregar -Wno-unused-parameters
-No -Wno-unused-parameters
a mis cflags, ya que nunca se usa en el caso de las funciones del módulo):
VALUE test(VALUE self, VALUE inp)
Su primer ejemplo usa un módulo como una cadena, que ciertamente no resultará en nada bueno. RSTRING_PTR
carece de verificaciones de tipo, lo cual es una buena razón para no usarlo.
Un VALOR es una referencia al objeto Ruby, pero no directamente un puntero a lo que puede contener (como un char * en el caso de una cadena). Necesitas obtener ese puntero usando algunas macros o funciones dependiendo de cada objeto. Para una cadena, desea que StringValuePtr
(o StringValueCStr
asegure de que la cadena esté terminada en nulo) que devuelve el puntero (no cambia el contenido de su VALOR de ninguna manera).
strlen(StringValuePtr(thing));
RSTRING_LEN(thing); /* I assume strlen was just an example ;) */
El contenido real de VALUE
es, al menos en MRI y YARV, el object_id
del objeto (o al menos, está después de un desplazamiento de bits).
Para sus propios objetos, el VALOR probablemente contendrá un puntero a un objeto C que puede obtener utilizando Data_Get_Struct
:
my_type *thing = NULL;
Data_Get_Struct(rb_thing, my_type, thing);