Ruby Language
Extensiones C
Buscar..
Tu primera extension
Las extensiones en C se componen de dos piezas generales:
- El código C en sí.
- El archivo de configuración de extensión.
Para comenzar con su primera extensión, coloque lo siguiente en un archivo llamado extconf.rb
:
require 'mkmf'
create_makefile('hello_c')
Un par de cosas para señalar:
Primero, el nombre hello_c
es el nombre de la salida de la extensión compilada. Será lo que uses junto con el require
.
En segundo lugar, el archivo extconf.rb
puede llamarse de cualquier manera, simplemente es lo que tradicionalmente se usa para construir gemas que tienen código nativo, el archivo que realmente compilará la extensión es el Makefile generado al ejecutar ruby extconf.rb
. El archivo Makefile predeterminado que se genera compila todos los archivos .c
en el directorio actual.
Coloque lo siguiente en un archivo llamado hello.c
y ejecute ruby extconf.rb && make
#include <stdio.h>
#include "ruby.h"
VALUE world(VALUE self) {
printf("Hello World!\n");
return Qnil;
}
// The initialization method for this module
void Init_hello_c() {
VALUE HelloC = rb_define_module("HelloC");
rb_define_singleton_method(HelloC, "world", world, 0);
}
Un desglose del código:
El nombre Init_hello_c
debe coincidir con el nombre definido en su archivo extconf.rb
, de lo contrario, al cargar dinámicamente la extensión, Ruby no podrá encontrar el símbolo para iniciar su extensión.
La llamada a rb_define_module
está creando un módulo Ruby llamado HelloC
cual vamos a poner un espacio de nombre bajo nuestras funciones en C.
Finalmente, la llamada a rb_define_singleton_method
hace un método de nivel de módulo vinculado directamente al módulo HelloC
que podemos invocar desde ruby con HelloC.world
.
Después de haber compilado la extensión con la llamada para make
que podamos ejecutar el código en nuestra extensión C.
¡Enciende una consola!
irb(main):001:0> require './hello_c'
=> true
irb(main):002:0> HelloC.world
Hello World!
=> nil
Trabajando con C Structs
Para poder trabajar con C structs como objetos Ruby, necesita envolverlos con llamadas a Data_Wrap_Struct
y Data_Get_Struct
.
Data_Wrap_Struct
envuelve una estructura de datos C en un objeto Ruby. Toma un puntero a su estructura de datos, junto con algunos punteros a las funciones de devolución de llamada, y devuelve un valor. La macro Data_Get_Struct
toma ese VALOR y le devuelve un puntero a su estructura de datos C.
Aquí hay un ejemplo simple:
#include <stdio.h>
#include <ruby.h>
typedef struct example_struct {
char *name;
} example_struct;
void example_struct_free(example_struct * self) {
if (self->name != NULL) {
free(self->name);
}
ruby_xfree(self);
}
static VALUE rb_example_struct_alloc(VALUE klass) {
return Data_Wrap_Struct(klass, NULL, example_struct_free, ruby_xmalloc(sizeof(example_struct)));
}
static VALUE rb_example_struct_init(VALUE self, VALUE name) {
example_struct* p;
Check_Type(name, T_STRING);
Data_Get_Struct(self, example_struct, p);
p->name = (char *)malloc(RSTRING_LEN(name) + 1);
memcpy(p->name, StringValuePtr(name), RSTRING_LEN(name) + 1);
return self;
}
static VALUE rb_example_struct_name(VALUE self) {
example_struct* p;
Data_Get_Struct(self, example_struct, p);
printf("%s\n", p->name);
return Qnil;
}
void Init_example()
{
VALUE mExample = rb_define_module("Example");
VALUE cStruct = rb_define_class_under(mExample, "Struct", rb_cObject);
rb_define_alloc_func(cStruct, rb_example_struct_alloc);
rb_define_method(cStruct, "initialize", rb_example_struct_init, 1);
rb_define_method(cStruct, "name", rb_example_struct_name, 0);
}
Y el extconf.rb
:
require 'mkmf'
create_makefile('example')
Después de compilar la extensión:
irb(main):001:0> require './example'
=> true
irb(main):002:0> test_struct = Example::Struct.new("Test Struct")
=> #<Example::Struct:0x007fc741965068>
irb(main):003:0> test_struct.name
Test Struct
=> nil
Escritura en línea C - Ruby en línea
RubyInline es un marco que te permite incrustar otros idiomas dentro de tu código de Ruby. Define el método en línea del Módulo #, que devuelve un objeto constructor. Le pasas al constructor una cadena que contiene un código escrito en un idioma que no es Ruby, y el constructor lo transforma en algo que puedes llamar desde Ruby.
Cuando se le da un código C o C ++ (los dos idiomas admitidos en la instalación predeterminada de RubyInline), los objetos del constructor escriben una pequeña extensión en el disco, la compilan y la cargan. No tiene que lidiar con la compilación usted mismo, pero puede ver el código generado y las extensiones compiladas en el subdirectorio .ruby_inline de su directorio de inicio.
Incruste el código C directamente en su programa Ruby:
- RubyInline (disponible como la gema rubyinline ) crea una extensión automáticamente
RubyInline no funcionará desde dentro de irb
#!/usr/bin/ruby -w
# copy.rb
require 'rubygems'
require 'inline'
class Copier
inline do |builder|
builder.c <<END
void copy_file(const char *source, const char *dest)
{
FILE *source_f = fopen(source, "r");
if (!source_f)
{
rb_raise(rb_eIOError, "Could not open source : '%s'", source);
}
FILE *dest_f = fopen(dest, "w+");
if (!dest_f)
{
rb_raise(rb_eIOError, "Could not open destination : '%s'", dest);
}
char buffer[1024];
int nread = fread(buffer, 1, 1024, source_f);
while (nread > 0)
{
fwrite(buffer, 1, nread, dest_f);
nread = fread(buffer, 1, 1024, source_f);
}
}
END
end
end
La función C copy_file
ahora existe como un método de instancia de Copier
:
open('source.txt', 'w') { |f| f << 'Some text.' }
Copier.new.copy_file('source.txt', 'dest.txt')
puts open('dest.txt') { |f| f.read }