Vala de plata

Hace un par de meses descubrí la existencia de Vala, un nuevo lenguaje de programación similar a C# pero con una característica muy interesante: en lugar de compilar directamente a código máquina o a código de una máquina virtual, compila a código C, usando GObject para implementar el sistema de clases y objetos. La gran ventaja de  esto es que permite crear bibliotecas y clases GObject sin necesidad de lidiar con la complejidad de este sistema, lo que, para los fans de Gnome nos resulta especialmente atractivo. También permite trabajar en un lenguaje moderno, lo suficientemente parecido a C# como para que casi no se note la diferencia, pero sin perder ni un ápice de rendimiento (lo siento, sigo sin creerme que un JIT consiga siempre mejores rendimientos, fuera de casos patológicos  y pruebas de laboratorio).

Otro detalle que me ha parecido fascinante es que simplifica mucho la gestión de memoria. Al contrario de lo que podría parecer, no utiliza un recolector de basura como el de Java, sino conteo de referencias. Lo interesante es que todo el proceso es muy transparente, pues el propio lenguaje se encarga de utilizarlo casi siempre. Así, un trozo de código como éste:

using GLib;

class cPrueba:Object {

}

int main() {

    cPrueba miObjeto,miObjeto2;

    miObjeto=new cPrueba();
    stdout.printf("Referencias: %udn",miObjeto.ref_count);
    miObjeto2=miObjeto;
    stdout.printf("Referencias: %ud %udn",miObjeto.ref_count,miObjeto2.ref_count);
    miObjeto=new cPrueba();
    stdout.printf("Referencias: %ud %udn",miObjeto.ref_count,miObjeto2.ref_count);
    return 0;
}

cuando se ejecuta, devuelve la siguiente salida por pantalla:

Referencias: 1d
Referencias: 2d 2d
Referencias: 1d 1d

Vemos que, cuando se hace la primera asignación, se crea un nuevo objeto de tipo cPrueba y se asigna a miObjeto, con lo que su contador de referencias es 1. Luego hacemos la segunda asignación. Como miObjeto y miObjeto2 son, realmente, punteros al mismo objeto (y apuntan, por tanto, a la misma zona de memoria), lo único que hace Vala es incrementar en uno el contador de referencias; por eso los contadores de referencias de miObjeto y miObjeto2 valen 2 en la segunda línea de la salida: porque en realidad son el mismo objeto, pero está referenciado por dos punteros.

Cuando, finalmente, asignamos un nuevo objeto a miObjeto, primero Vala libera el objeto al que apunta, lo que en la práctica consiste en decrementar su contador de referencias. Si en ese momento éste pasase a valer cero, entonces ese objeto quedaría huérfano (ningún puntero apunta a él), por lo que Vala procedería a destruirlo automáticamente, liberando su memoria; sin embargo, en este ejemplo eso no ocurre porque miObjeto2 está apuntando al objeto viejo, así que lo único que ocurre es que su contador de referencias pasa a valer 1. Por su parte, se crea un nuevo objeto de tipo cPrueba, apuntado por miObjeto, cuyo contador de referencias también vale 1, como vemos en la tercera línea de la salida.

Si observamos el código fuente generado (usando valac -C prueba.vala) su función MAIN queda como:

gint _main (void) {
    cPrueba* miObjeto;
    cPrueba* miObjeto2;
    cPrueba* _tmp0;
    cPrueba* _tmp2;
    cPrueba* _tmp1;
    cPrueba* _tmp3;
    gint _tmp4;
    miObjeto = NULL;
    miObjeto2 = NULL;
    _tmp0 = NULL;
    miObjeto = (_tmp0 = cprueba_new (), (miObjeto == NULL) ? NULL : (miObjeto = (g_object_unref (miObjeto), NULL)), _tmp0);
    fprintf (stdout, "Referencias: %udn", ((GObject*) miObjeto)->ref_count);
    _tmp2 = NULL;
    _tmp1 = NULL;
    miObjeto2 = (_tmp2 = (_tmp1 = miObjeto, (_tmp1 == NULL) ? NULL : g_object_ref (_tmp1)), (miObjeto2 == NULL) ? NULL : (miObjeto2 = (g_object_unref (miObjeto2), NULL)), _tmp2);
    fprintf (stdout, "Referencias: %ud %udn", ((GObject*) miObjeto)->ref_count, ((GObject*) miObjeto2)->ref_count);
    _tmp3 = NULL;
    miObjeto = (_tmp3 = cprueba_new (), (miObjeto == NULL) ? NULL : (miObjeto = (g_object_unref (miObjeto), NULL)), _tmp3);
    fprintf (stdout, "Referencias: %ud %udn", ((GObject*) miObjeto)->ref_count, ((GObject*) miObjeto2)->ref_count);
    return (_tmp4 = 0, (miObjeto == NULL) ? NULL : (miObjeto = (g_object_unref (miObjeto), NULL)), (miObjeto2 == NULL) ? NULL : (miObjeto2 = (g_object_unref (miObjeto2), NULL)), _tmp4);
}

(He omitido toda la parte de la definición GObject de la clase ejemplo porque no aporta nada). Vemos en las distintas líneas como cada vez que se hace una asignación de un objeto a una variable, primero se procede a liberar lo que hubiese en dicha variable utilizando una llamada a g_object_unref, para asegurarse de que el contador de referencias decrece correctamente. Luego llama a g_object_ref con la variable que se copia para incrementar su contador de referencias. Como vemos, este proceso automático nos simplifica la vida lo suficiente como para que nos podamos olvidar de la gestión automática de la memoria…

O casi, porque existe, al menos, un caso en el que no se cumple todo ésto: las listas. Sin embargo, tras buscar y rebuscar, encontré que es un bug en Vala, así que ya está reportado (http://bugzilla.gnome.org/show_bug.cgi?id=586577) y debería estar corregido en breve, asi que no añadais una llamada a unref a mano, sino simplemente esperad a que lo corrijan y recompilad para eliminar el memory leak.

Existe, de todas formas, una opción alternativa, que es utilizar LibGee y su ArrayList. Este tipo de listas no tiene el bug que comento, por lo que, si se utiliza, no hay riesgo de memory leaks.

Pese a todo, es importante señalar un par de detalles sobre las listas de Glib en Vala: si vemos la documentacion, hay varias formas de declararlas, en concreto:

  • var milista = List<contenido_de_lista>();
  • List<contenido_de_lista> milista = List<contenido_de_lista>();
  • List milista = List<contenido_de_lista>();

Siendo contenido_de_lista una clase de elementos que contendra la lista. Asi, si quiero hacer una lista de strings la definiria como List<string>.  La cuestion es que, en base a las pruebas que he hecho, solo se realiza gestion de memoria si la lista se define como en la primera o la segunda opcion, porque solo ahi el compilador podra estar seguro de que el contenido es una clase derivada de Object y tiene, por tanto, contador de referencias. Si definimos la lista como en el tercer caso no se realizara gestion de memoria, por lo que podriamos incluso encontrarnos con que metemos en una lista global un elemento definido localmente y que, al terminar la funcion en donde se definio dicho elemento y volver a la funcion principal, dicho elemento es liberado, consiguiendo un hermoso core dump en cuanto intentemos acceder a dicho dato en la lista. Por tanto, mi consejo es utilizar siempre listas bien definidas, salvo que se sepa perfectamente lo que se esta haciendo.

Gemelos

Acabo de lanzar dos nuevas versiones de mis programas.

El primero es HTMLProc. Al principio sólo iba a corregir el uso de os.Popen2 (está obsoleto en Python 2.6), pero al probarlo descubrí que sólo añadía el tamaño a las imágenes añadidas en las páginas principales, pero no en las imágenes de las páginas añadidas con el comando include. Ya está corregido y aproveché para subir de nuevo todas las páginas corregidas.

El segundo es DeVeDe. La lista de cambios está en su sección, así que poco voy a comentar, salvo un par de detalles:

  • Ahora permite reempaquetar un fichero sin recomprimir: ésto es muy útil para ficheros VOB, los cuales están en formato MPEG-2 y, por tanto, en teoría deberían poder utilizarse «tal cual» con DeVeDe. Por desgracia, si se usan activando la opción «Este fichero ya está en formato MPEG-PS» DeVeDe falla. La razón es que es un PS con algunos campos extra, lo que hace que DVDAuthor se lie y falle. Al activar esta opción, DeVeDe extrae el audio y el vídeo y los vuelve a meter en un PS, pero sin recomprimirlo, simplemente copiandolo; de esta manera no tiene campos extra, y además no se pierde ni un ápice de calidad porque no se está descomprimiendo y volviendo a comprimir.
  • Elimina ficheros viejos en caso de que existiesen previamente, al usar LN: si se hacía una previsualización de un fichero con la opción «Este fichero ya está en formato MPEG-PS» y se cancelaba la conversión, nunca se borraba el enlace, por lo que si se intentaba hacer otra previsualización, fallaba.
  • Ahora soporta ficheros de subtítulos cuyos nombres contengan caracteres reservados en XML: en efecto, antes, si un nombre de fichero de subtítulos tenía un caracter específico de XML, DeVeDe fallaba.
  • Ahora ya no se cuelga al añadir un vídeo en un formato desconocido: en versiones anteriores, cuando se añadía un vídeo en un contenedor conocido pero con un codec desconocido, al intentar obtener los parámetros del vídeo (tamaño, tasa, etc) MPlayer lanzaba una gran cantidad de mensajes de error que saturaban la salida de error, lo que hacía que DeVeDe se colgase. Un ejemplo es un fichero FLV de Flash: el contenedor es reconocido por MPlayer, pero el contenido no.

Que lo disfruteis con salud.

Con gettext y a lo loco

Hoy estaba preparando la nueva versión de DeVeDe para enviarla a los traductores y, cuando ejecuté el script para actualizar los ficheros de cadenas salió un error: no podía encontrar el fichero devede.glade. Obvio, porque había cambiado a GtkBuilder, así que quité el fichero en POTFILES.IN y añadí todos los nuevos. Sin embargo, me encontré con que ahora no encontraba ni la mitad de las frases.

Una rápida búsqueda en google me dio la solución: por defecto, intltool-update confunde los ficheros .ui de glade con descripciones ui de Bonobo. Para solucionarlo no hay más que añadir la cadena [type: gettext/glade] antes de cada nombre de fichero de glade en POTFILES.IN.

Microerrores

Acabo de sacar una microrevisión de DeVeDe, la 3.13.1, en donde corrijo un par de fallos… fallitos… despistes realmente.

Para empezar, ahora ya se puede grabar la estructura del DVD (faltaba una w al crear la ventana). Por otro lado, la ventana de propiedades de un título se cerraba cada vez que se cambiaba la acción. Ambos fallos debidos a la migración a GtkBuilder. Por último, he corregido un detalle que impedía copiar la fuente de letra para subtítulos cuando se instalaba desde un paquete DEB, con lo que si nunca se había ejecutado antes, no se podía utilizar.

A disfrutarlo.

De uno a otro confín

Hoy voy a poner una bonita foto que hice hace unos días, a ver qué os parece:

Si pinchais sobre ella la podreis ver a tamaño real (8192 x 1616 pixels).

Esta panorámica de Vigo la hice a partir de cinco fotos, uniéndolas luego con Hugin, un programa libre específico para este tipo de tareas. El proceso fue prácticamente automático, no tuve que tocar nada. Es cierto que si uno se fija, puede ver un par de zonas en donde se nota un poco la unión, pero también ha influído el hecho de que no sólo roté la cámara, sino que también me tuve que desplazar por las características del sitio desde donde las saqué.

La licencia de uso de esta foto es Creative Commons BY-NC-SA: Creative Commons License
Esta obra está bajo una licencia de Creative Commons

Mal número si no crece

Me refiero, obviamente, a la versión 13 de DeVeDe, que estaba terminada y prometida desde finales del mes pasado. No la publiqué antes no sólo por movidas personales, sino también porque necesita de la versión 2.16 de GTK, que sólo está disponible en la nueva Ubuntu.

Los cambios son pequeños, muy pequeños: para empezar, unas correcciones en la traducción a griego, y por otro lado la comentada migración de LibGlade a GtkBuilder.

En la próxima versión quiero hacer algunos cambios internos en el código que convierte los archivos, para que sea más sencillo añadir nuevos formatos de salida. También espero añadir nuevas opciones, como por ejemplo permitir hacer DVDs sin menú principal, o añadir por defecto ficheros de subtítulos que haya en el mismo directorio que una película, para poder hacer más cosas con un simple Arrastrar y Soltar. También quiero retocar un poco más la interfaz para que sea más utilizable en equipos con pantallas pequeñas (como los netbooks).

¡Que la disfruteis!

Ubuntu y el Asus F5GL

Actualizado el fichero de prioridades. Finalmente conseguí hacer que el Asus F5GL de mi amigo funcione como es debido. Para ello, ha «bastado» con instalar la versión 9.04 (la actual estable) y añadir el núcleo de la versión 9.10, con el correspondiente sistema de pinning para que se actualice pero sin tocar nada del resto del sistema. Para los que tengan el mismo problema, explicaré cual es el proceso:

Seguir leyendo Ubuntu y el Asus F5GL

Tiempos modernos

Actualizado: A veces, por casualidad, cuando sólo pretendes pasar un buen rato, te tropiezas con una obra maestra. Hablo nada menos que de Tiempos modernos, el film de Charles Chaplin. En tono de comedia, aunque con un trasfondo dramático (pues, como dijo Roberto Benigni en una entrevista, la comedia sin drama no es comedia, sino bufonada), realiza una gran crítica social de la Gran Depresión, en los años 30; un período, en mi opinión, de grandes paralelismos con el actual por culpa de la (maldita) crisis que estamos sufriendo.

No diré nada más sobre ella porque, como cualquier otra gran obra, lo mejor es pasar de leer críticas y verla directamente. Por eso lo único que haré será animaros a que la consigais como podais, bien comprándola en alguna oferta de grandes almacenes, pidiéndosela prestada a un amigo, o bajándotela de tu eMule más próximo. O, como comentó Anónimo en el primer comentario, podeis probar en la Biblioteca más próxima.

Llega GtkBuilder

Actualizado. Acabo de terminar una nueva versión de DeVeDe; sin embargo no os abalanceis como locos a por ella porque todavía no está disponible. La razón es que uno de los (pocos) cambios que hice fue migrar el código a GtkBuilder; el problema es que varias de las funcionalidades que aporta se han añadido en la nueva GTK 2.16, la cual viene en Ubuntu 9.04, la cual no sale oficialmente hasta mañana. Eso significa que si saco ahora esta nueva versión, nadie podrá usarla a menos que (como yo) instale una versión Release Candidate de Ubuntu. Por esa razón no la sacaré hasta el sábado (por aquello de dar tiempo a la gente a que migre de manera calmada y ordenada).

Sin embargo, aprovecho para escribir una breve introducción a GtkBuilder (breve porque tampoco hay mucho que explicar, es muy sencilla de usar).

¿Qué es GtkBuilder?

GtkBuilder es una biblioteca que llevará a cabo las mismas funciones que actualmente realiza LibGlade: a partir de una serie de ficheros XML creados con Glade u otro editor, generará las ventanas de una aplicación, incluyendo todos sus widgets internos.

¿Pero si va a hacer las mismas funciones, por qué sacar una nueva biblioteca en lugar de seguir con la vieja?

Por una razón muy simple: LibGlade no forma parte de GTK, sino que sigue un desarrollo completamente independiente. Eso la limita en algunos aspectos (por ejemplo, velocidad a la hora de soportar nuevos widgets y propiedades). Por otro lado, no tenía sentido que una biblioteca tan fundamental fuese un elemento externo. Por último, aunque LibGlade siempre funcionó muy bien y está bien implementada, había algunas cosas que se podían mejorar.

Por todo ésto los desarrolladores de GTK decidieron crear una biblioteca integrada en GTK y que fuese lo más parecida posible a LibGlade, con el objetivo de reemplazarla: GtkBuilder había nacido.

Trabajando con GtkBuilder

La forma de trabajar con GtkBuilder es muy similar a LibGlade. La principal diferencia es que en GtkBuilder no es posible generar sólo una parte del árbol (aunque se están planteando el soportar esta opción). Esto significa que es más eficiente poner cada ventana en un fichero XML independiente.

Si ya tienes un fichero .glade puedes convertirlo fácilmente al nuevo formato usando el script gtk-builder-convert. Como primer parámetro recibe el fichero .glade a convertir; como segundo parámetro, el fichero de salida en formato GtkBuilder; por último, opcionalmente se le puede pasar un parámetro -r seguido del identificador de un widget (usualmente una ventana), de manera que sólo convertirá dicho widget y todos sus hijos. Esta última opción permite separar una antigua interfaz Glade con todas las ventanas en un único fichero, en varios ficheros GtkBuilder, cada uno con una sola ventana.

Un ejemplo: gtk-builder-convert viejo_proyecto.glade ventana_ppal.ui -r main_win creará un fichero ventana_ppal.ui que contendrá la ventana main_win y todos sus elementos, del proyecto viejo_proyecto.glade.

Por cierto, la extensión de los nuevos archivos es .ui. Es un detalle que me costó encontrar.

La primera gran diferencia con Glade es que no hace falta importar ningún módulo, pues ya está dentro de GTK.

Trabajar con GtkBuilder es tan sencillo como:

class MiClase:
    __init__(self):
        builder=gtk.Builder()
        builder.set_translation_domain("mi_aplicacion")
        builder.add_from_file("path/to/file.ui")
        builder.connect_signals(self)
        ventana=builder.get_object("nombre_ventana")

En la primera línea creamos un objeto de tipo GtkBuilder; en la segunda especificamos cual es el nombre de nuestra aplicación de cara a usar GETTEXT para las locales; a continuación le indicamos qué fichero .ui contiene la definición de la interfaz que queremos generar. Estas tres líneas reemplazan a la única línea que utilizábamos con LibGlade.

En la siguiente línea le indicamos que conecte las señales con los métodos de nuestra clase; es igual que el método signal_autoconnect de LibGlade.

Por último, pedimos una referencia a un objeto concreto; es igual que el viejo método get_widget de LibGlade.

Trabajando con Glade

Las nuevas versiones de Glade-3 no sólo soportan el nuevo formato, sino que incluso trabajan por defecto en él. En general se sigue trabajando exactamente igual que antes; la única diferencia es que es recomendable que cada ventana vaya en un fichero (y, por tanto, en un proyecto) diferente.

Un detalle muy interesante es la comprobación del soporte de versiones de GTK en el nuevo Glade: si tenemos abierta una interfaz y escogemos Editar->Preferencias, nos saldrá el siguiente cuadro de diálogo:

glade

En la parte inferior vemos la opción Versiones de los toolkit necesarias. Dicha opción nos permite comprobar si en esta interfaz estamos usando algun widget que no esté soportado por alguna versión antigua de GTK o de GtkBuilder. Esto es precisamente lo que me ocurrió con esta versión de DeVeDe: el nuevo constructor de menús sólo está soportado a partir de GTK 2.16, que viene a partir de Ubuntu 9.04.

En otra entrada explicaré como se trabaja con las GtkComboBox y sus listas asociadas, pues GtkBuilder permite generar todo desde Glade, sin necesidad de picar apenas código.