Volviendo a C: Usando libcdio y extendiendo a Ruby
Como creo que se han dado cuenta, si hay un lenguaje que me gusta es Ruby. Todos mis últimos proyectos personales los he hecho en este lenguaje y no podría estar más complacido por ello.
Ahora bien, como todo lenguaje interpretado y relativamente nuevo, no existen muchas bibliotecas para hacer virguerías extrañas. En mi caso, hace tiempo estoy trabajando en un programa de línea de comando para clasificar mis canciones repartidas entre más de 90 CD y DVD, en los más variados formatos: mp3, ogg, flac, ape, mpc.
En Windows, hay un estupendo programa llamado MediaMonkey, pero en Linux no he pillado nada que se le parezca. De ahí la motivación para mi pequeño programita. Para no sufrir demasiado, hasta ahora Catori (que así se llama el programa) funciona como "glue" para variados programas de línea de comando, como ogginfo, metaflac e id3tag, quienes entregan la metainformación sobre los archivos. Pero lo que tenía mesándomes los cabellos era conseguir la etiqueta y alguna forma de sacar un hash del CD.
Cdio
Buscando y buscando, me di cuenta que cdio era la biblioteca que necesitaba. Recurriendo a los viejos conocimientos de C y copiando descaradamente de los ejemplos logré sacar algo como esto
- #include <stdio.h>
- #include <string.h>
- #include <sys/types.h>
- #include <cdio/cdio.h>
- #include <cdio/iso9660.h>
- #include <cdio/cd_types.h>
- void imprimir_info(iso9660_pvd_t *pvd);
- int
- main (int argc, const char *argv[]) {
- char dev[] ="/dev/cdrom";
- CdIo *p_cdio = cdio_open (dev,DRIVER_UNKNOWN);
- if(NULL!=p_cdio) {
- discmode_t discmode=cdio_get_discmode (p_cdio);
- if(discmode==CDIO_DISC_MODE_CD_DA) {
- printf("El disco es de audio\n");
- } else {
- printf("El disco es de datos\n");
- iso9660_pvd_t pvd;
- // Tengo que averiguar de que tipo es la primera pista, por lo menos.
- bool modo2;
- track_t pista1=cdio_get_first_track_num(p_cdio);
- //track_format_t formato_pista1=cdio_get_track_format(p_cdio, pista1);
- modo2=cdio_get_track_green(p_cdio, pista1);
- if(modo2) {
- printf("Es una pista modo2\n");
- cdio_read_mode2_sector (p_cdio, &pvd, ISO_PVD_SECTOR, false);
- } else {
- printf("Es una pista modo1\n");
- cdio_read_mode1_sector (p_cdio, &pvd, ISO_PVD_SECTOR, false);
- }
- imprimir_info(&pvd);
- }
- } else {
- printf("Error al tratar de leer el disco");
- }
- return 0;
- }
- void imprimir_info(iso9660_pvd_t *pvd) {
- fprintf(stdout, "Application: %s\n", iso9660_get_application_id(pvd));
- fprintf(stdout, "Preparer : %s\n", iso9660_get_preparer_id(pvd));
- fprintf(stdout, "Publisher : %s\n", iso9660_get_publisher_id(pvd));
- fprintf(stdout, "System : %s\n", iso9660_get_system_id(pvd));
- fprintf(stdout, "Volume : %s\n", iso9660_get_volume_id(pvd));
- fprintf(stdout, "Volume Set : %s\n", iso9660_get_volumeset_id(pvd));
- }
Necesitan compilarlo con libcdio y libiso9660, por si aca. La cosa es que funciona. Todavía tengo que ver como sacar el hash, pero creo que bastaría con unir el número de bytes de las pistas con una muestra de los primeros datos para sacar algo que sirva.
Extendiendo Ruby
Ahora, antes de crear un wrapper a cdio(de lo cual hablaremos en otro post), es necesario aprender a extender Ruby.
La verdad, es MUY FÁCIL. Lo primero, es crear un archivo llamado extconf.rb que contenga el siguiente código, para una clase de prueba que llamaremos ... Clase.
- require 'mkmf'
- create_makefile("Clase");
- #include <stdio.h>
- #include <stdlib.h>
- #include <fcntl.h>
- #include <sys/ioctl.h>
- #include <string.h>
- #include <ruby.h>
- VALUE cClase;
- static VALUE
- clase_init(VALUE self) {
- VALUE dato;
- dato=rb_str_new2("Vacio");
- rb_iv_set(self,"@dato",dato);
- return self;
- }
- static VALUE
- clase_get(VALUE self,VALUE oObject) {
- VALUE dato;
- rb_iv_set(self,"@dato",oObject);
- return oObject;
- }
- static VALUE
- clase_info(VALUE self) {
- VALUE dato;
- VALUE retorno;
- char * info;
- dato=rb_iv_get(self,"@dato");
- switch(TYPE(dato)) {
- case T_OBJECT:
- info="Es un objeto";
- break;
- case T_STRING:
- info="Es un string";
- break;
- case T_FLOAT:
- info="Es un flotante";
- break;
- case T_FIXNUM:
- info="Es un numero";
- break;
- case T_NIL:
- info="Es nulo";
- break;
- default:
- info="No tengo idea que es esta hueva";
- }
- retorno=rb_str_new2(info);
- return retorno;
- }
- Init_Clase()
- {
- VALUE cClase = rb_define_class("Clase",rb_cObject);
- rb_define_method(cClase, "initialize",clase_init,0);
- rb_define_method(cClase, "get",clase_get,1);
- rb_define_method(cClase, "info",clase_info,0);
- }
No es tan terrible como parece, eh?
Todos los objetos en Ruby los ve C como un struct de tipo VALUE. Por tanto, primero defino mi clase en el espacio global con el nombre cClase. La función de inicialización se llama Init_XXX, donde XXX es el nombre de la Clase.
Dentro de la función de inicialización creamos las clases, métodos, propiedades, constantes y módulos que desemos que el sistema vea. En mi caso, creo la clase Clase, que deriva de Object y tres métodos, initialize, get e info. Cada uno de los métodos hace referencia a un método de C, los cuales defino en forma previa para el compilador no se vuelva loco.
El uso de las funciones es muy sencillo, ya que todo se pasa y se devuelve con VALUE. Por ejemplo, miren como hago que la propiedad @dato contenga un objeto pasado a la función get
- static VALUE
- clase_get(VALUE self,VALUE oObject) {
- VALUE dato;
- rb_iv_set(self,"@dato",oObject);
- return oObject;
- }
Muy sencillo! la función toma como parámetros dos VALUE, el primero un puntero a la estructura del objeto Clase y el segundo el objeto que va como parámetro desde Ruby. Después, simplemente asigno este valor a @dato con rb_iv_set.
La función info, que me entrega información sobre el valor almacenado en @dato no creo que merezca mucho comentario. La macro TYPE me devuelve la constante correspondiente al tipo de objeto que contiene un VALUE, así que con el switch hago el clásico reconocimiento para ver que tipo de variable fue la que se me pasó.
Para finalizar, el script que prueba la clase y la salida
- require "Clase"
- clase=Clase.new
- clase.get(2)
- puts clase.info
- clase.get("Hola")
- puts clase.info
- clase.get(Module.new)
- puts clase.info
El resultado:
>ruby test.rb Es un numero Es un string No tengo idea que es esta hueva
Definitivamente, amo a Ruby!
- 1998 lecturas

Hola!
muy interesante el post!
te pongo algún enlace más sobre la relación entre lenguajes de script y C/C++
Clases de ruby embedidas en c++ y viceversa
SWIG (Simplified Wrapper and Interface Generator): Un generador de interfaces y "wrappers" que, en principio, soporta Perl, Python, Tcl/Tk, Ruby, C#, Common Lisp (Allegro CL), Java, Modula-3 y OCAML. No está mal, ¿verdad?
Un saludo
Gracias por tu comentario.
Muy interesante es el tema del ruby embebido en C++.
Traté de usar Swig con Ruby y la verdad no entendí mucho; me salió mucho más claro hacer todo a mano. Ahora, como hacer extensiones en PHP parece mucho más complicado, creo que por ahí me podría ser de utilidad :)
Enviar un comentario nuevo