Muchas gemas usan extensiones para envolver bibliotecas que están escritas en C con un envoltorio de Ruby.
Los ejemplos incluyen nokogiri que envuelve libxml2 y libxslt, pg que es una interfaz para la base de datos PostgreSQL y las gemas mysql y mysql2 que proporcionan una interfaz para la base de datos MySQL.
Crear una gema que use una extensión implica varios pasos.
Esta guía se centra en lo que debes poner en la especificación de tu gema para que sea lo más fácil de mantener posible.
La extensión en esta guía envolverá malloc() y free() de la biblioteca estándar de C.
Diseño de GEMAS
Cada gema debe comenzar con un Rakefile que contiene las tareas que necesitan los desarrolladores para trabajar en la gema.
Los archivos de la extensión deben ir en el directorio ext/ en un directorio que coincida con el nombre de la extensión.
Para este ejemplo, usaremos “my_malloc” para el nombre.
Algunas extensiones se escribirán parcialmente en C y parcialmente en ruby. Si vas a admitir varios idiomas, como extensiones C y Java, debes colocar los archivos ruby específicos de C en el directorio ext/ y también en el directorio lib/.
Rakefile
ext/my_malloc/extconf.rb # extension configuration
ext/my_malloc/my_malloc.c # extension source
lib/my_malloc.rb # generic features
Cuando se construya la extensión, los archivos ext/my_malloc/lib/ se instalarán en el directorio lib/ por ti.
Extconf.rb
El extconf.rb configura un Makefile que se encarga de construir tu extensión.
El extconf.rb debe verificar las funciones, macros y bibliotecas compartidas necesarias de las que depende la extensión.
El extconf.rb debe salir con un error si falta alguno de estos.
Aquí hay un extconf.rb que busca malloc() y free() creando un Makefile que instalará la extensión construida en lib/my_malloc/my_malloc.so:
require "mkmf"
abort "missing malloc()" unless have_func "malloc"
abort "missing free()" unless have_func "free"
create_makefile "my_malloc/my_malloc"
Consulte la documentación de mkmf y de la extension.rdoc para obtener más información sobre cómo crear un extconf.rb y para obtener documentación sobre estos métodos.
Extensión C
La extensión C que envuelve malloc() y free() entra ext/my_malloc/my_malloc.c.
Aquí está el listado:
#include
struct my_malloc {
size_t size;
void *ptr;
};
static void
my_malloc_free(void *p) {
struct my_malloc *ptr = p;
if (ptr->size > 0)
free(ptr->ptr);
}
static VALUE
my_malloc_alloc(VALUE klass) {
VALUE obj;
struct my_malloc *ptr;
obj = Data_Make_Struct(klass, struct my_malloc, NULL, my_malloc_free, ptr);
ptr->size = 0;
ptr->ptr = NULL;
return obj;
}
static VALUE
my_malloc_init(VALUE self, VALUE size) {
struct my_malloc *ptr;
size_t requested = NUM2SIZET(size);
if (0 == requested)
rb_raise(rb_eArgError, "unable to allocate 0 bytes");
Data_Get_Struct(self, struct my_malloc, ptr);
ptr->ptr = malloc(requested);
if (NULL == ptr->ptr)
rb_raise(rb_eNoMemError, "unable to allocate %ld bytes", requested);
ptr->size = requested;
return self;
}
static VALUE
my_malloc_release(VALUE self) {
struct my_malloc *ptr;
Data_Get_Struct(self, struct my_malloc, ptr);
if (0 == ptr->size)
return self;
ptr->size = 0;
free(ptr->ptr);
return self;
}
void
Init_my_malloc(void) {
VALUE cMyMalloc;
cMyMalloc = rb_const_get(rb_cObject, rb_intern("MyMalloc"));
rb_define_alloc_func(cMyMalloc, my_malloc_alloc);
rb_define_method(cMyMalloc, "initialize", my_malloc_init, 1);
rb_define_method(cMyMalloc, "free", my_malloc_release, 0);
}
Esta extensión es simple con solo unas pocas partes:
struct my_mallocpara mantener la memoria asignada.my_malloc_free()para liberar la memoria asignada después de la recolección de basura.my_malloc_alloc()para crear el objeto contenedor de Ruby.my_malloc_init()para asignar memoria de ruby.my_malloc_release()para liberar memoria de ruby.Init_my_malloc()para registrar las funciones en la claseMyMalloc.
Ahora podemos crear la clase real MyMalloc y vincular métodos recién definidos en Ruby ( lib/my_malloc.rb es el lugar correcto para eso) por ejemplo:
class MyMalloc
VERSION = "1.0"
end
require "my_malloc/my_malloc"
Puede probar la construcción de la extensión de la siguiente manera:
$ cd ext/my_malloc
$ ruby extconf.rb
checking for malloc()... yes
checking for free()... yes
creating Makefile
$ make
compiling my_malloc.c
linking shared-object my_malloc.bundle
$ cd ../..
$ ruby -Ilib:ext -r my_malloc -e "p MyMalloc.new(5).free"
#<0x007fed838addb0>0x007fed838addb0>