Programación en el entorno GNOME |
---|
Los hilos permiten que un programa realice varias tareas simultáneamente. Tradicionalmente, los sistemas operativos tipo UNIX™/linux™ han usado los procesos para este propósito. No obstante, éstos son mucho más pesados (requieren más recursos) que los hilos.
Sin embargo, cada sistema operativo ha implementado de una forma diferente el concepto de hilo y, como resultado, las implementaciones no son compatibles, aunque sean muy parecidas. Ésto implica que si, se desea usar hilos en el programa será necesario proveer de una implementación diferente para cada sistemas operativo tipo UNIX™.
GLib™ solventa este problema ocultando las implementaciones especificas de cada plataforma y proveyendo de un API que puede ser accedido de la misma forma desde cualquier sistema operativo. No obstante, internamente se usará las llamadas nativas disponibles en la plataforma donde se encuentre la aplicación. Es decir, GLib™ abstrae la implementación de los hilos.
Antes de entrar en materia, es necesario hacer una observación: todos los hilos que un programa crea, comparten el mismo espacio de direcciones; esto es, se podrá acceder a las variables globales de nuestro programa (para lectura y escritura) desde cualquier hilo.
Antes de poder usar cualquier función de manejo de hilos, hay que preparar el sistema para ejecutar estas operaciones, es decir, es necesario inicializar el sistema de hilos, nada más sencillo.
Void g_thread_init ( | vtable) ; |
GThreadFunctions * | vtable ; |
En la mayoría de las ocasiones bastará con que el parámetro pasado al sistema de hilos sea nulo, de esta forma GLib™ se encargará de buscar los parámetros más apropiados.
Es importante no llamar a esta función más de una vez pues, si esto sucediese, la ejecución de nuestro programa sería abortada. Para evitar esto la forma más sencilla es hacer:
if (!g_thread_supported ()) g_thread_init (NULL);
Después, el sistema estará listo para realizar cualquier operación que requiera el uso de hilos. Para empezar, se creará un hilo:
GThread *hilo:
hilo = g_thread_create (funcion_hilo, NULL, FALSE, NULL);
A partir de este punto, el procedimiento funcion_hilo se ejecutará en un hilo diferente, el programa continuará su ejecución normal y, en paralelo a él, se ejecutará esta función.
Como ya se ha dicho, las variables globales del programa van a poder ser accedidas desde cualquier hilo. Esto es una gran ventaja, pues compartir información entre todas las hilos será muy sencillo; no obstante, todo tiene su parte negativa: habrá que tener cuidado a la hora de acceder a estos valores, porque puede que un hilo intente modificar alguna variable cuando otro hilo está leyendo esa misma variable, en cuyo caso, el resultado sería impredecible. Es necesario que los hilos se sincronicen para acceder a estas variables.
Existen varios métodos los sincronización disponibles:
Cerrojos (Mutex)
Cerrojos estáticos (Static Mutex)
Variables Condición
Cerrojos: son llaves mutuamente exclusivas, que permiten conocer si se puede o no acceder a una determinada variable. Si una variable está protegida por un cerrojo, se podrá consultar el estado de la llave para conocer si está siendo accedida por otro hilo en ese instante. Si no lo estuviese, será posible obtener la llave, hacer las operaciones precisas sobre esa variable, y después abrir la llave de nuevo para que otro hilo pueda acceder a la variable.
g_mutex_new g_mutex_lock g_mutex_trylock g_mutex_unlock g_mutex_free
Los cerrojos estáticos (Static Mutex) son iguales que las anteriores, pero no necesitan ser creadas en tiempo de ejecución; son creadas cuando el programa es compilado.
g_static_mutex_init g_static_mutex_lock g_static_mutex_trylock g_static_mutex_unlock g_static_mutex_get_mutex g_static_mutex_free
Condiciones:
Antes de explicar el uso de las funciones que proporciona GLib™ para el uso de cadenas UTF-8, sería conveniente repasar una serie de conceptos que serán útiles para una mejor conmprensión.
ISO establece un estándar en el que define el conjunto de caracteres universal conocido como UCS. Este conjunto de caracteres asegura la compatibilidad con otros conjuntos de caracteres, de forma que se puedan usar distintas funciones para pasar de un determinado lenguaje a UCS y de este al lenguaje original sin perder contenido.
Gracias al Unicode™ se pueden representar la enorme mayoría de lenguajes conocidos, tales como latín, griego, cirílico, hebreo y así un largo etcétera. Unicode™ es un conjunto de caracteres de 16 bits, es decir, posee 65536 posibles combinaciones, representando cada una de estas combinaciones un carácter.
Un valor o carácter UCS o Unicode™, indistintamente llamado de una forma u otro, englobará todos estos lenguajes, partiendo en otras codificaciones más pequeñas como BMP (Basic Multilingual Plane) o Plane 0, subconjunto de 16 bits, al igual que otros como UTF-16, de 16 bits, o UTF-8, de 8 bits, ambos utilizados posteriormente por la GLib™ tanto para comprobaciones de tipos de caracteres como para conversiones entre distintos subconjuntos Unicode™, tales como UTF-8 y UTF-16.
Al principio, se intentaron dar dos intentos de conseguir un conjunto de caracteres: el proyecto de estandarización ISO 10646 antes comentado y el proyecto Unicode™, pero pronto se comprobó que no era bueno tener dos proyectos en marcha y los dos conjuntos de caracteres diferentes se unieron formando una única tabla de caracteres que fue la de Unicode™ con las extensiones dadas por la ISO 10646 pasando a ser llamado indistintamente Unicode™ o ISO 10646. Con el paso del tiempo ha habido modificaciones de Unicode™ adiciones de caracteres y clasificaciones en subconjuntos como pueden ser el ampliamente usado ASCII™, UTF-8, UTF-16 y BMP entre otros.
En GLib™ existe soporte para UTF-8 debido a sus compatibilidades con el resto de códigos, ya que su codificación engloba en sus primeros 7 bits el tan conocido ASCII™.
La codificación UTF-8 está definida en el ISO 10646.
Los primeros 7 bits son utilizados para la codificación de ASCII™, para la compatibilidad con éste por su uso extendido. De forma qu,e desde 0000 a 007F (128 caracteres), se usan para la codificación ASCII™ de 7 bits.
Los caracteres mayores a 007F son utilizados para el soporte a otros lenguajes.
Los bytes FE y FF no son utilizados para la codificación UTF-8.
En determinadas ocasiones, los programas necesitan el uso de más de un juego de caracteres para un correcto tratamiento de los datos que maneja. Para que esto sea posible sin conflictos ni confusiones, GLib™ proporciona un útil conjunto de funciones para la conversión entre juegos de caracteres.
Con todo lo explicado anteriormente, es evidente que el juego de caracteres usado para representar nombres de archivos y el juego de caracteres UTF-8 que podamos usar en nuestros programas puede diferir bastante. Para solventar esto existen las funciones g_filename_to_utf8 y g_filename_from_utf8, que realizan la conversión de nombre de archivo a UTF-8 y de UTF-8 a nombre de archivo, respectivamente.
gchar * g_filename_to_utf8 ( | cadena_a_convertir, | |
longitud_de_cadena, | ||
bytes_leidos, | ||
bytes_escritos, | ||
error) ; |
const gchar * | cadena_a_convertir; |
gssize | longitud_de_cadena; |
gsize * | bytes_leidos; |
gsize * | bytes_escritos; |
GError ** | error; |
gchar * g_filename_from_utf8 ( | cadena_a_convertir , | |
longitud_de_cadena, | ||
bytes_leidos, | ||
bytes_escritos, | ||
error) ; |
const gchar * | cadena_a_convertir ; |
gssize | longitud_de_cadena; |
gsize * | bytes_leidos; |
gsize * | bytes_escritos; |
GError ** | error; |
Como se puede observar, el uso de las dos funciones es idéntica porque devuelven la cadena convertida al nuevo juego de caracteres o NULL en caso de que ocurra un error. Los parámetros bytes_leidos, bytes_escritos y error son parámetros de salida, devolviendo respectivamente los bytes convertidos con éxito, los bytes que ocupa la cadena tras la transformación y el posible error que se pueda producir.
De forma parecida al tratamiento de nombres de archivo, las funciones g_locale_to_utf8 y g_locale_from_utf8 convierten cadenas en el juego de caracteres usado internamente por el sistema a cadenas en UTF-8 y viceversa.
gchar * g_locale_to_utf8 ( | cadena_a_convertir, | |
longitud_de_cadena, | ||
bytes_leidos, | ||
bytes_escritos, | ||
error) ; |
const gchar * | cadena_a_convertir; |
gssize | longitud_de_cadena; |
gsize * | bytes_leidos; |
gsize * | bytes_escritos; |
GError ** | error; |
gchar * g_locale_from_utf8 ( | cadena_a_convertir, | |
longitud_de_cadena, | ||
bytes_leidos, | ||
bytes_escritos, | ||
error) ; |
const gchar * | cadena_a_convertir; |
gssize | longitud_de_cadena; |
gsize * | bytes_leidos; |
gsize * | bytes_escritos; |
GError ** | error; |
El uso de las funciones es idéntico al descrito con g_filename_to_utf8 y g_filename_from_utf8.
Además de las funciones de conversión de cadenas, GLib™ provee además de un conjunto de funciones destinadas a servir de ayuda y apoyo al tratamiento de cadenas en UTF-8.
La función g_get_charset es útil para conocer el juego de caracteres que esta usando el sistema sobre el que se ejecuta el programa, pudiendo evitar transformaciones redundantes.
gboolean g_get_charset ( | juego_de_caracteres ) ; |
G_CONST_RETURN gchar ** | juego_de_caracteres ; |
La función devuelve TRUE si el juego de caracteres que usa el sistema es el UTF-8 y FALSE en cualquier otro caso. juego_de_caracteres es un parámetro de salida que devulve el juego de caracteres usado por el sistema en formato cadena.
La función g_utf8_validate sirve determinar si una cadena de caracteres está ya en formato UTF-8 o no.
gboolean g_utf8_validate ( | cadena_a_validar, | |
longitud, | ||
final) ; |
gchar * | cadena_a_validar; |
gssize | longitud; |
const gchar ** | final; |
El parámetro cadena_a_validar es la cadena que se quiere validar. longitud es el número máximo de caracteres que queremos que valide, puede ser NULL si queremos validar la cadena entera; por último, en el último parámetro, la función devuelve un puntero al último carácter que ha podido validar. La función devuelve TRUE o FALSE dependiendo de si la cadena pasada cumple el formato UTF-8 o no.
Una de las cosas que pueden interesar a la hora de hacer una aplicación es crear un método por el cual se pueda aplicar extensiones. Ésto se conoce por el nombre de plugin. Para los desarrolladores habituales, el término "plugin" no debería resultar desconocido, ya que existen aplicaciones en GNOME, como por ejemplo gedit, gnumeric o algunos reproductores multimedia que aprovechan este método para extender la funcionalidad de la aplicación.
Para entender cómo funcionan los plugins, hay que tener claro el concepto de "símbolo". En una librería, a cada función o variable se le da un nombre único (símbolo). Los símbolos que interesan ahora, son los que se exportan, que serán los que se utilicen para enlazarla. Por ejemplo, cuando en un código se llama a una función, lo que sehace es especificar el símbolo de la librería y, en consecuencia, a la función que se desea. Ésto se hace automáticamente a la hora de programar. Pero también se puede hacer manualmente. O sea, se puede especificar una librería en concreto (el plugin) y llamar a una función en concreto. De este modo se podría crear muchas librerías (plugins), que además implementen una función común en todas ellas y se podría cargar manualmente esa función de cualquiera de los plugins. Para luego ejecutarla dentro de el programa. Ésto se verá mucho más claro más adelante, con el ejemplo de esta sección.
Para realizar este tipo de acciones se dispone de GModuleÉste es un sistema que permitirá la carga dinámica de símbolos. Además, siguiendo con una de las máximas de GLib™, este sistema es portable. Lo que nos permitirá cargar símbolos en sistemas como Linux™, Solaris™, Windows™, HP-UX™.
Para la utilización de GModule es necesario tener presente dos cosas. La primera es que, en el momento de la compilación, se debe indicar que se va a usar GModule, y para ello se indicará en el pkg-config que incluya la librería gmodule-2.0. Y por supuesto incluir la linea:
#include <gmodule.h>
La otra tiene que ver con detectar si el sistema operativo en el que se trabaja, soporta este tipo de acciones. Y para ello se usará la función g_module_supported que detectará si se puede hacer uso de las siguientes funciones y devolverá TRUE si es así.
gboolean g_module_support (
)
;
void ;
Si el sistema en el que se trabaja soporta el uso de GModule, el siguiente paso será abrir la librería de la cual posteriormente se extraerá el símbolo deseado. Para ello se debe indicar de alguna manera la posición en nuestro sistema de la librería (plugin) que se desea abrir. Y con este fin, se usara la función g_module_build_path, con la que podremos construir la ruta exacta donde se encuentra la misma. Esta función devolverá una cadena con la ruta anteriormente nombrada.
gchar * g_module_build_path ( | directorio, | |
nombre_del_modulo ) ; |
const gchar * | directorio; |
const gchar * | nombre_del_modulo ; |
En el primer parámetro de esta función se debe especificar el directorio donde se encuentra la librería que se desea abrir. Y en el segundo, el nombre de la misma. Pero hay que tener claro que sólo el nombre, ya que la función se encargará de poner la extensión de la librería según el sistema operativo en el que este corriendo GLib™. Por ejemplo, en Linux™ colocará la extensión ".so" y en Windows™ ".dll".
Una vez que se obtiene la ruta devuelta por g_module_build_path, lo siguiente será cargar la librería. Y para ello está la siguiente función.
GModule * g_module_open ( | ruta_de_la_libreria , | |
bandera ) ; |
const gchar * | ruta_de_la_libreria ; |
GModuleFlags | bandera ; |
El parámetro bandera puede contener dos valores, un cero o la macro G_MODULE_BIND_LAZY. El primer valor, el cero, significa que, cuando se ejecuta esta función, se cargan todos los símbolos de la librería. Mientras que con el segundo valor, sólo lo hará cuando sea necesario.
Otra cosa a tener en cuenta es el resultado de esta función. Como se puede observar, devuelve un puntero a una estructura GModule. Esta estructura es opaca a la visión del desarrollador, pero servirá en posteriores funciones como referencia a la librería que hemos abierto. En el caso de que GModule sea igual a NULL, ésto significará que la función no lo ha conseguido.
Ahora ha llegado el momento de obtener el símbolo que se desee de la librería. Como se dijo antes, estos símbolos pueden ser funciones, estructuras o variables que hayan sido exportadas en la librería (plugin), así que, de momento, se dará por hecho que existe el símbolo al que se va a llamar con la siguiente función.
gboolean g_module_symbol ( | modulo , | |
símbolo , | ||
puntero_al_simbolo ) ; |
GModule * | modulo ; |
const gchar * | símbolo ; |
gpointer * | puntero_al_simbolo ; |
Esta función recibirá como parámetros el puntero a la estructura GModule, que hace referencia a la librería que anteriormente se ha abierto. Después se introduce el nombre del símbolo que se desea obtener y, para terminar, la función devolverá en su tercer parámetro un puntero al símbolo que se haya solicitado. En el caso que se haya solicitado una función, devolverá un puntero a esa función. Y si lo que se ha solicitado es una variable devolverá un puntero a la misma. Para terminar sólo queda decir que el valor boolean que devuelve, verifica si la función se ha cumplido con éxito o no.
Con las funciones anteriores se ha clarificado el trabajo que se ha de realizar para llamar al símbolo de la librería (plugin). Ahora queda tratar la parte contraria, la del plugin. Se ha de tener en cuenta que para que la función g_module_symbol encuentre el símbolo que se reclame, este ha de estar declarado de alguna manera. Y para este acto se usa la macro G_NODULE>G_MODULE_EXPORT. Esta macro, escrita delante de una variable o función, realiza la función que se desea, la de exportar el símbolo. De este modo, ya será posible referenciar el símbolo deseado desde la función g_module_symbol.
Dentro de la preparación de plugin con GModule existe una manera de inicializar el plugin. Para ello se deberá declarar una función llamada g_module_check_init. Dentro de esa función, es posible declarar acciones para la inicialización del plugin. Pero esta función deberá seguir estrictamente este prototipo.
const gchar* (*GModuleCheckInit)
( | modulo ) ; |
GModule * | modulo ; |
En el momento en el que se abra la librería (plugin) con la función g_module_open, ésta buscará esta función y la ejecutará. En el caso que esta función no esté declarada, simplemente habrá abierto la librería. En el caso de que exista la función, podrá retornar NULL si ha sido exitosa la inicialización o un cadena de caracteres con la información del error contenida en ella.
Y al igual que se puede especificar una manera de inicializar el plugin, también se puede especificar la manera en la que finalizará. Para ello se deberá especificar una función llamada g_module_unload. Y, al igual que la anterior, deberá seguir el siguiente prototipo.
void (*GModuleUnload)
( | modulo ) ; |
GModule * | modulo ; |
Llegados a este punto, es hora demostrar prácticamente todo lo anteriormente explicado. Para ello se usará el siguiente ejemplo, que consta de cuatro archivos. Uno de ellos es un Makefile, que nos ayudará a compilar dicho ejemplo y hará más familiar el uso de la herramienta make.
El siguiente ejemplo explicará cómo funciona un plugin. Para ello se cuenta, por un lado, con una aplicación, correspondiente a los ficheros modulo.c y modulo.h. Esta aplicación llamará al plugin generado por el fichero plugin.c, que cuando lo compilemos se llamará libplugin.so. La aplicación exportará una estructura llamada info_plugin y una función llamada escribe. Dentro de la aplicación, se llamará a los símbolos anteriormente citados y se modificará el contenido de la estructura. Una vez ese contenido ha sido modificado, se llamará al puntero a función correspondiente al símbolo de la función escribe. Cuando esta función se ejecuta dentro de la aplicación, lo que realmente se esta ejecutando es la función escribe, que imprime por pantalla el contenido de la estructura.
Ejemplo 37. Cabecera del Módulo
#ifndef __MODULO_H__ #define __MODULO_H__ #include <glib.h> #include <gmodule.h> typedef struct _datos_plugin datos_plugin; struct _datos_plugin { gchar *nombre; gchar *correo; }; #endif /* __MODULO_H__ */
Ejemplo 38. Código del Módulo
#include <stdlib.h> #include "modulo.h" /* * Se define un tipo, puntero a función nula, para * después declarar una variable (escribe), a la que * se asignará un símbolo(en este caso una función) del plugin. * */ typedef void (*Func_escritura) (void); int main (int argc, char **argv) { gchar *dir; GModule *modulo; gchar *plugin; datos_plugin **info; Func_escritura escribe; /* Variable que apuntará al símbolo */ /* Se comprueba que el sistema soporta los plugins */ if (!g_module_supported ()) return 0; dir = g_get_current_dir (); /* Se obtiene el path del plugin que se usará */ plugin = g_module_build_path (dir, "libplugin"); g_free (dir); /* Se carga el plugin */ modulo = g_module_open (plugin, G_MODULE_BIND_LAZY); if (!modulo) g_error ("error: %s", g_module_error ()); /* Se obtiene el símbolo info_plugin, una estructura de datos */ if (!g_module_symbol (modulo, "info_plugin", (gpointer *) & info)) g_error ("error: %s", g_module_error ()); (*info)->nombre = "Pepe Lopez"; /* Aquí se usa el símbolo */ (*info)->correo = "pepe@gnome.org"; /* info_plugin */ /* Se obtiene el símbolo plugin_escribe, una función */ if (!g_module_symbol (modulo, "plugin_escribe", (gpointer *) & escribe)) g_error ("error: %s", g_module_error ()); escribe (); /* Aquí se usa el símbolo plugin_escribe */ /* Se descarga el plugin */ if (!g_module_close (modulo)) g_error ("error: %s", g_module_error ()); return EXIT_SUCCESS; }
Ejemplo 39. Ejemplo de plugin
#include "modulo.h" /* * Se declara un puntero a una estructura de datos * y se exporta como símbolo, para poder ser * utilizado por las aplicaciones que carguen * este plugin * */ G_MODULE_EXPORT datos_plugin *info_plugin; /* * Esta función inicializa el plugin. Se exporta como simbolo. * * Se ejecuta al ser cargado el plugin -> g_module_open() * */ G_MODULE_EXPORT const gchar * g_module_check_init (GModule * module) { g_print ("--- Inicializacion del plugin ---\n\n"); info_plugin = g_malloc (sizeof (datos_plugin)); return NULL; } /* * Esta función finaliza el plugin. Se exporta como simbolo. * * Se ejecuta al ser descargado el plugin -> g_module_close() * */ G_MODULE_EXPORT void g_module_unload (GModule * module) { g_print ("\n--- Finalizacion del plugin ---\n"); g_free (info_plugin); } /* * Esta función será un símbolo del plugin * que se podrá llamar a voluntad en cualquier * momento, una vez se haya cargado el plugin. * */ G_MODULE_EXPORT void plugin_escribe (void) { g_return_if_fail (info_plugin != NULL && info_plugin->nombre != NULL && info_plugin->correo != NULL); g_print ("Nombre : %s \ncorreo-e : %s\n", info_plugin->nombre, info_plugin->correo); }
Y el resultado de ejecutar el programa obtenido de compilar, tanto el plugin, como el modulo con make, sería el siguiente:
bash$:/var/CVS/librognome/book/code/GModule$ ./modulo --- Inicializacion del plugin --- Nombre : Pepe Lopez correo-e : pepe@gnome.org --- Finalizacion del plugin ---
<< Estructuras de datos avanzadas. | Sistema de objetos de GLib. >> |