Estamos listos para comenzar una nueva charla este domingo será sobre GLib: nivel avanzado en donde se verá el gestor de errores threads (hilos) y plug-ins (extensiones) a cargo de Rodrigo Moya les recuerdo las consultas las canalizan a través de mí rodrigo, cuando quieras bien hola lo primero, decirle a gpoo que me puede cortar con preguntas cuando quiera Gestión de errores en las charlas anteriores, creo recordar que se comentó el uso de GError para recibir información d eerrores de las funciones de glib hay algunas funciones (no todas) que reciben un parámetro de tipo: GError **ret_error que es un puntero a un puntero a GError por ejemplo: GDir * g_dir_open (const gchar *path, guint flags, GError **error); (g_dir_open se usa para abrir un directorio, para obtener una lista de los ficheros contenidos en él) el parámetro "error" es como os comentaba antes la forma de usar esta (y todas las funciones que usen este método) es muy sencilla: GError *error = NULL; dir = g_dir_open ("/home/rodrigo", 0, &error) se le pasa un puntero a un puntero a GError que hemos inicializado a NULL al retornar, g_dir_open habrá rellenado el parámetro "error" con la información del error producido, si es que se produjo alguno. Es decir: ...llamada a g_dir_open... if (error) { // Se ha producido un error si no se produce ningun error, el parámetro "error" seguirá siendo NULL cuando se produce un error lo que hace g_dir_open (y demás funciones que usan este método) es crear una instancia de la estructura GError definida en gerror.h: struct _GError { GQuark domain; gint code; gchar *message; }; ahora explicaremos cómo crear nuevos GError para usar este método en nuestras apps pero en cuanto al uso de este método de obtención de información de errores todo lo que hay qye saber es lo mencionado antes: GError *error = NULL; / llamada a la función con ....&error) if (error) // se ha producido un error simplemente añadir a esto que, cuando "error" no sea NULL podemos mostrar mensajes de error más concretos que "ha habido un error" o cosas por el estilo podemos obtener el mensaje de error y su código: if (error) { g_print ("Error %d: %s\n", error->code, error->message); para terminar, decir que una vez que no necesitemos más el GError retornado debemos librerarlo, puesto que la función que nos devuelve el error creó una instancia de GError por tanto: if (error) { g_print ("Error %d: %s\n", error->code, error->message); g_error_free (error); } ahora vamos a ver cómo usar esto en nuestras aplicaciones imaginemos que tenemos una función que hace una operación (cualquiera) void hacer_operacion (tipo1 param1, tipo2 param2, ... GError **error) { // hacer lo que sea.. if (se_ha_producido_un_error) { *error = g_error_new (g_quark_from_static_string ("Mi aplicación"), codigo_de_error, "Mensaje de error"); } esta función hace lo mismo que g_dir_open si se produce un error, crea un GError con la info referente al error producido para crear errores, hay dos funciones: GError* g_error_new (GQuark domain, gint code, const gchar *format, ...); GError* g_error_new_literal (GQuark domain, gint code, const gchar *message); ambas son similares se diferencian en que una (g_error_new) permite el uso de un formato igual que el de printf: error = g_error_new (g_quark_from_static_string ("Mi aplicación"), codigo_de_error, "Error %d producido en hacer_operacion", codigo_de_error); mientras que la otra (g_error_new_literal) acepta una única cadena de texto que represernta el mensaje de error el primero parámetro de ambas es de tipo GQuark que es otro tipo de datos que se usa para asociar enteros->cadenas esto se usa para almacenar listas de cadenas (constantes) normalmente en este caso, lo que se hace es asociar, a cada GError un dominio de errores, que es, básicamente, el nombre de la aplicación puesto que un GQuark es una asociación entero->cadena simplemebte creamos un nuevo GQuark a partir de una cadena constante que en este caso, es el nombre de la aplicación "Mi aplicación" tambien, en el caso de librerías, se usa el nombre de la librería como dominio esto es importante, puies nos permite diferenciar dónde se produjo el error hay distintas funciones para la gestión de GError's: void g_error_free (GError *error); GError* g_error_copy (const GError *error); gboolean g_error_matches (const GError *error, GQuark domain, gint code); void g_set_error (GError **err, GQuark domain, gint code, const gchar *format, ...) G_GNUC_PRINTF (4, 5); void g_clear_error (GError **err); creo que no necesitan mucha explicación así que, si alguien no las entiende que pregunte ahora o calle para siempre :-) al parecer se entiende todo --- gnomero fija modos [#gnome-hispano +o slack] --- slack fija modos [#gnome-hispano -o slack] bien pues seguimos con hilos Hilos (threads) rodrigo, antes de continuar el codigo_de_error es un numero arbitrario, cierto? en principio, si la cosa es usar algo que te permita identificar el error puesto que aun no hay una lista de códigos en GLib cada aplicación usará el que quiera basicamente enumeraciones si ok, todo claro supongo que lo ideal es usar los códigos de erorr de la libc bien Hilos (threads) Supongo que todos sabeis que las apps normalmente usan un único hilo de ejecución esto quiere decir que la aplicación se ejecuta secuencialmente y si una parte se queda bloqueada (esperando por E/S , etc) toda la app se queda bloqueada por supuesto, hay otros métodos para solventar esto de hecho, yo no soy muy partidario del uso de threads salvo en casos en los que su uso es la única solución (o la menos mala) por ejemplo, la E/S se puede hacer no bloqueante de forma muy sencilla los threads permiten que una aplicación tenga varios hilos de ejecución conucrrentes por supuesto, una sola instrucción (en máquinas con 1 procesador) será ejecuta cada vez pero el programa mantiene varios hilos concurrentes de forma que si uno se queda bloqueado, el resto de hilos siguen su ejecución el añadir varios hilos de ejecución suena maravilloso a primera vista porque parece que abre muchas posibilidades pero, evidentemente, tambien conlleva nuevos problemas especialmente el acceso concurrente a variables del programa que es el mayor quebradero de cabeza al usar threads no pretendo desanimar a nadie para que no use threads simplemente aviso de sus problemas, y de mi opinión sobre ellos pues no quiero que nadie salga de aquí pensando que sion LA solución cuando no es cierto -ekeko- Ximian Evolution es un ejemplo de múltiples hilos? son la solución a algunos problemas, pero no a todos :-) ekeko: la parte del mailer si usa múltiples hilos ekeko: uno para el UI, y 'n' para las operaciones no-UI el resto de componentes no por cierto, ya que mencionas evolution muchas veces, al hacer un ps se ven cosas como: $ ps ax | grep evolution-mail 782 ? S 1:43 evolution-mail --oaf-activate-iid=OAFIID:GNOME_Evolution_Mail_ShellComponent --oaf-ior-fd=16 849 ? S 0:00 evolution-mail --oaf-activate-iid=OAFIID:GNOME_Evolution_Mail_ShellComponent --oaf-ior-fd=16 850 ? S 0:10 evolution-mail --oaf-activate-iid=OAFIID:GNOME_Evolution_Mail_ShellComponent --oaf-ior-fd=16 851 ? S 0:07 evolution-mail --oaf-activate-iid=OAFIID:GNOME_Evolution_Mail_ShellComponent --oaf-ior-fd=16 852 ? S 0:12 evolution-mail --oaf-activate-iid=OAFIID:GNOME_Evolution_Mail_ShellComponent --oaf-ior-fd=16 854 ? S 0:31 evolution-mail --oaf-activate-iid=OAFIID:GNOME_Evolution_Mail_ShellComponent --oaf-ior-fd=16 857 ? S 0:11 evolution-mail --oaf-activate-iid=OAFIID:GNOME_Evolution_Mail_ShellComponent --oaf-ior-fd=16 858 ? S 0:04 evolution-mail --oaf-activate-iid=OAFIID:GNOME_Evolution_Mail_ShellComponent --oaf-ior-fd=16 912 ? S 1:42 evolution-mail --oaf-activate-iid=OAFIID:GNOME_Evolution_Mail_ShellComponent --oaf-ior-fd=16 18772 ? S 0:01 evolution-mail --oaf-activate-iid=OAFIID:GNOME_Evolution_Mail_ShellComponent --oaf-ior-fd=16 parece que hay 'n' copias de evolution-mail cuando realmente hay una con múltiples hilos bueno, para usar hilos en GLib, lo primero que hay que hacer es: g_thread_init (NULL); esto inicializa el soporte de threads en GLib esta función sólo puede ser llamada una vez así que, para asegurarnos de que así sea: if (!g_thread_supported ()) g_thread_init (NULL); esta función aborta el programa si no hay soporte de threadsa en el sistema por cierto, las funciones de g_threas estan en la librería libgthread-2.0, no libglib2.0 por tanto, para usar estas funciones, tenemos que compilar con: gcc .... `pkg-config --libs gthread-2.0` para añadir la librería adicional a nuestro programa una vez que está inicializado el sistema de threads se pueden crear nuevos threads, con: GThread* g_thread_create (GThreadFunc func, gpointer data, gboolean joinable, GError **error); esta función devuelve un nuevo GThread despues de realmente inicializar un nuevo hilo de ejecución en el que se hace una llamada a la función especificada en el parámetro 'func' que es de tipo GTHreadFunc: gpointer (*GThreadFunc) (gpointer data); el parámetro 'data' en g_thread_create es el que luego es pasado a "func" joinable especifica si deseamos poder esperar a que el thread termine (luego lo vemos) mientras que "error", como ya habeis adivinado, se usa para recibir info del error que se produzca un ejemplo: GThread *th; GError *error = NULL; th = g_thread_create ((GThreadFunc) mi_thread, NULL, TRUE, &error); if (error) { g_print ("Error: %s\n", error->message); g_error_free (error); } la función mi_thread sería: gpointer mi_thread (gpointer user_data) { // hacer lo que sea return valor_de_retorno_del_thread); } una vez que se salga de la función mi_thread el thread es terminado inmediatamente el valor_de_retorno_del_thread se usa para poder enviar de vuelta información del estado de finalización del thread para obtener esta info de finalización del thread 1) debemos crear los threads con 'joinable' = TRUE 2) debemos usar, desde otras partes de la app, la función g_thread_join esta función (g_thread_join) permite esperar a que un thread termine y obtener dicha info dicha 'info' es un gpointer, como podeis ver, de forma que es completamente específico de la app que estemos desarrollando puede ser un puntero a un código de error (entero), un GError, o cualquier otra cosa que queramos para terminar threads, como he comentado, se sale de la fucnión especificada en la llamada a g_thread_create (mi_thread) pero tambien pueden ser terminados, normalmente desde fuera del propio thread (fuera de mi_thread), usando la función g_thread_exit perdón, esta función sólo puede ser llamada desde el propio thread :-) void g_thread_exit (gpointer retval); o sea, sólo desde dentro de mi_thread tambien podemos poner prioridades a los threads de forma que unos se ejecuten antes que otros: void g_thread_set_priority (GThread *thread, GThreadPriority priority); donde: typedef enum { G_THREAD_PRIORITY_LOW, G_THREAD_PRIORITY_NORMAL, G_THREAD_PRIORITY_HIGH, G_THREAD_PRIORITY_URGENT } GThreadPriority; dentro de mi_thread, podemos obtener fácilmente una referencia al GThread creado para ese hilo en concreto con: GThread* g_thread_self (void); así, por ejemplo: gpointer mi_thread (gpointer user_data) { g_thread_set_priority (g_thread_self (), G_THREAD_PRIORITY_URGENT); tambien podemos ceder tiempo de proceso a otros threads: void g_thread_yield (void) desde fuera de los threads, para esperar a que termine: gpointer valor_de_retorno_del_thread; valor_de_retorno_del_thread = g_thread_join (th); en valor_de_retorno_del_thread obtendremos el valor de retorno devuelto por mi_thread para la gestión del acceso a datos por múltiples hilos, se usan los GMutex (de Mutual Exclusion) que es una forma de bloquear el acceso a datos del programa popr ello, lo mejor, si queremos acceso concurrente a datos compartidos por toda la aplicación (ojo, compartidos no me refiero a variables globales, si no a posiciones de memoria a las que todos acceden) debemos crear un GMutex: GMutex* g_mutex_new (); esto simplemente crea un nuevo GMutex cuando queramos acceder a los datos compartidos: GMutex *mutex = g_mutex_new (); g_mutex_lock (mutex); g_mutex_lock bloquea el acceso al GMutex pero lo interesante de esta función es que, al llamarla desde dentro de un hilo, si el GMutex ya está bloqueado por otro hilo, esta función no retorna hasta que el otro hilo desbloquee el GMutex es decir: gpointer mi_thread (gpointer user_data) { g_mutex_lock (mutex_global); ... procesar datos compartidos g_mutex_unlock (mutex); ... por supuesto, es muy mala idea bloquear el mutex al principio del thread y no desbloquearlo hasta el final esto significaría que el mutex en cuestión no permitiría el acceso a los datos a otros hilos hasta terminar por ello, hay que poner especial atención en sólo bloquear cuando realmente se vaya a hacer el acceso: while (lo:_qu while (lo_que_sea) { .. obtenemos datos de una fuente externa, por ejemplo ... // ahora, actualizamos los datos compartidos g_mutex_lock (mutex_global); ... g_mutex_unlock (mutex_global); ... } en vez de: g_mutex_lock (mutex_global); while ... { ... } g_mutex_unlock (mutex_global); creo que se ve la diferencia de lo que quiero decir ¿no? alguna pregunta sobre threads? al parecer no, a mi me ha parecido todo muy claro ok Habria que liberar el GMutex? si, g_mutex_free adelante rodrigo Módulos (o plugins) los módulos (o plugins) permiten, normalmente, extender una aplicación son simples librerías compartidas, que podemos cargar desde nuestras apps seguro que muchos ya habeis usado dlopen, dlclose... que son funciones para hacer eso de unix glib, como con otras cosas, ofrece una forma portable de todas estas cosas comunes de todos los sistemas al igual que gthread, gmodule es una lib extra por tanto, hay que usar pkg-config --libs gmodule-2.0 para enlazar con ella antes de usar las funciones de gmodule, debemos saber si el sistema en el que se ejecuta la app lo soporta (GLib tiene soporte para todos los unix con dlopen, para HP-UX via shl_load, y para windows con sus .dll por tanto, antes de cargar ningun módulo: if (!g_module_supported ()) g_error ("No hay soporte para GModule\n"); una vez comprobada la existencia del soporte podemos cargar nuevos módulos con g_module_open: GModule* g_module_open (const gchar *file_name, GModuleFlags flags); el primer parámetro es el fichero (.so) que representa el módulo a cargar puesto que el nombre de esos ficheros es dependiente del sistema (.so/.dll...) podemos usar la función: gchar* g_module_build_path (const gchar *directory, const gchar *module_name); así: gchar *path = g_module_build_path (directorio_de_plugins, "plugin"); GModule *module = g_module_open (path, 0); g_free (path); g_module_build_path construye el nombre de la librería a cargar en el caso de linux, la llamada anterior devolvería suponiendo que directorio_de_plugins = /usr/lib/plugins, por ejemplo: /usr/lib/plugins/libplugin.so en el caso de windows: ...\plugin.dll ... con g_module_open no solo podemos cargar librerías si no que tambien podemos cargar un ejecutable y hacer llamadas a sus funciones normalmente, usaremos librerías, que es lo más comun uyna vez obtenido, con g_module_open un GModule válido sólo nos queda obtener punteros a las funciones/datos del módulo eso se hace con: gboolean g_module_symbol (GModule *module, const gchar *symbol_name, gpointer *symbol); por ejemplo, para cargar la función gtk_button_new_with_label de libgtk-2.0.so: GModule *module: module = g_module_open ("/usr/lib/libgtk-2.0.so", 0); if (!module) g_error ("No se pudo cargar gtk-2.0"); typedef GtkWidget * (* ProtoGtkButtonNewWithLabel) (const gchar *label); ProtoGtkButtonNewWithLabel func_new_button_with_label: g_module_symbol (module, "gtk_button_new_with_label", &func_new_button_with_label); Gtkwidget *boton = (* func_new_button_with_label) ("Hola"); lo que hacemos es declarar un tipo de datos que es un puntero a función con la misma firma (ProtoGtkButtonNewWithLabel) que la función a cargar y pasamos un puntero a una variable de ese tipo donde obtenemos el puntero a la función carga desde el módulo Gtkwidget *boton = (* func_new_button_with_label) ("Hola"); en esta línea, llamamos a la función cargada desde el módulo cuya dirección ha sido almacenada en func_new_button_with_label para cargar variables desde el módlo el mecanismo es el mismo pero en cambio de usar punteros a funciones, usamos punteros a estructuras you otros tipos de datos básicos por ejemplo: gint entero_cargado_desde_el_modulo; g_module_symbol (module, "entero_declarado_en_el_modulo", &entero_cargado_desde_el_modulo); entero_cargado_desde_el_modulo = 37; perdón: gint *entero_cargado_desde_el_modulo; g_module_symbol (module, "entero_declarado_en_el_modulo", &entero_cargado_desde_el_modulo); *entero_cargado_desde_el_modulo = 37; una vez que no se necesite más el módulo se cierra con: g_module_close (module); una vez que se haga esto, los símbolos (funciones, variables) cargados desde el módulo serán inválidos, y por tanto no se podrán usar más se puede hacer que un módulo permanezca suempre en memoria: void g_module_make_resident (GModule *module); esta función hace que las llamadas a g_module_close sobre el módulo hecho "residente" sean ignoradas y por tanto el módulo permanece en memoria durante todo el programa es interesante que gmodule, cuando carga un módulo busca unas funciones: const gchar *g_module_check_init (GModule *module); void g_module_unload (GModule *module) si estas funciones estan presentes en el .so que se carga con g_module_open g_module_check_init será llamada cuando se cargue el módulo (g_module_open) y g_module_unload será llamada cuando se descargue (g_module_close) y esto es todo preguntas? No veo claro lo de uload despues de close jmx: g_module_unload se llama antes de descargar completamente el módulo o sea, el módulo sigue estando en memoria cuando _unload es llamada g_module_unload no es una función de libgmodule si no una función presente en los .so si esas funciones existen en el .so, son llamadas desde libgmodule si no, no se hace ninguna llamada desde g_module_open/_close Cuando cargamos un modulo lo hacemos en el espacio de trabajo de nuestra aplicacion (no se comparten con otras aplicaciones) jmx: si, lo cargas desde el segmento de memoria de tu aplicación es decir, desde otros procesos, no se puede acceder a las funciones que tu has cargado pero si pueden acceder, a través de GModule, al módulo eso si, sería una copia nueva del módulo parece que no hay mas preguntas pues bien nada mas darle gracias a rodrigo e invitarlos para la proxima semana para aprender el sistema de objetos