Charlas de GNOME Hispano: Glib intermedio

Relator: Germán Poo Caamaño (gpoo)
Moderador: Alejandro Sánchez Acosta (alex)
Domingo 15 de diciembre de 2002, 21:30 horas UTC
irc.gnome.cl #gnome-hispano

alex
Hola, buenas tardes!
Un domingo más comenzamos con las charlas aquí en GNOME Hispano.
La charla de hoy la dará German Poo y tratará sobre GLib Intermedio.
En caso de loguear la charla no olviden poner en modo conferencia su cliente
 de irc y para cualquier pregunta pueden remitirmela a mi por privado.

Sin más, germán, adelante.
--- gnomero fija modos [#gnome-hispano +o kiwnix]
--- kiwnix fija modos [#gnome-hispano -o kiwnix]
gpoo
gracias alex
En esta charla veremos:
- Gestión de memoria dinámica
- Gestión de archivos
- Uso de sockets
- Estructuras de datos
por tiempo, y dada la naturaleza de la charla anterior puede ser que estructuras


 de datos no podamos verlo
La charla pasada trató de "glib básico", en donde Roberto Majadas
(telemaco) abordó los temas de tipos de datos, mensajes de salida
y manejo de cadenas.
icha charla la pueden leer en línea, desde el sitio de 
GNOME Hispano, cuya URL es:
http://www.es.gnome.org/eventos/charlairc-20021208-glib_basico.php

Antes de comenzar, daré algunas reseñas que nos sitúen dentro
del contexto de las charlas.
Cuando decimos que enseñaremos a programar con Glib, GTK+, GNOME,
etc. nos referimos a crear programas usando las funciones que
nos permitan disponer de una aplicación (principalmente gráfica)
con integración con el escritorio GNOME.
Recordemos que glib es una biblioteca que nos provee funciones y
tipos de datos que nos permiten mejorar la portabilidad de nuestras
aplicaciones y simplificar la programación.  Podríamos verlo como
una extensión a las funciones clásicas de C, mucho mas ricas
y fáciles de emplear.
Por lo tanto, se debe disponer de las bibliotecas de desarrollo de 
glib, en debian el paquete se llama libglib2.0-dev en RedHat se 
llama glib2-devel.  No es intención de la charla enseñar como se instalan.

Los programas que aquí veremos podremos compilarlos como:
$ gcc -g -Wall `pkg-config --cflags --libs glib-2.0` -o programa programa.c

Donde "programa.c" es el nombre nuestro código fuente y "programa"
es el nombre que tendrá nuestra aplicación una vez compilada.  Para
verificar que nuestro programa este sintácticamente bien escrito,
indicamos al compilador que nos avise de todo lo que parezca raro
y que podría repercutir en un error en la ejecución, es es
decir
-Wall que significa "Warging all".
Por último, y nos menos importante, lo que veremos sólo es un 
complemento del Libro GNOME, con el cual pueden guiarse para
crear sus propias aplicaciones. La URL es:
http://libros.es.gnome.org/librognome/librognome/librognome/book1.html

estamos listos para comenzar
Gestión de memoria dinámica
Las funciones principales de gestión de memoria son
g_malloc, g_malloc0 y g_free.
g_malloc permite solicitar un espacio de memoria al sistema operativo,

tìpicamente lo empleamos cuando manejamos alguna puntero a estructura,

punteros a char, etc.  Es decir, nos retorna un puntero a una zona
de memoria reservada.
Es muy importante liberar (devolver al sistema operativo) toda la memoria
 
que solicitamos con g_malloc.  De lo contrario
nuestra aplicación tendrá "memory leaks", es decir, comenzará a agotar

la memoria y nuestro sistema se puede volver más lento (será más
notorio si nuestra aplicación es grande).
Entonces, para liberar la memoria reservada usaremos g_free.
g_malloc y g_free son los equivalentes en C de malloc y free, pero completamente
 portables.
y que seguramente, más de algún dolor de cabeza les habrá reportado alguna
 vez
El formato de uso es:
gpointer g_malloc (gulong size);
gpointer g_malloc0 (gulong size);
void g_free (gpointer mem);
La diferencia entre g_malloc y g_malloc0, es que esta última
además de reserva "size" bytes de memoria, los inicializa en 0,
que es lo más probable que deseemos.
Ejemplo:
#include <glib.h>
int
main (int argc, char **argv)
{
    gchar *text;
 gint len = strlen ("Hola mundo") + 1;
    text = g_malloc (len);
    g_snprintf (text, len, "Hola mundo");
    _print ("%s\n", text);
    g_free (text);
    return 0;
}
veo un error, pase a escribir _print en vez de g_print
por cada g_malloc que hagamos, debemos proveer un g_free
para liberar la memoria
Además, existen g_new y gnew0, que son meras interfaces de
g_malloc y g_malloc0 respectivamente, y que nos permiten
reservar "n" veces el tamaño de una estructura.
Así por ejemplo, podemos tener:
GString *str;
str = g_new0 (GString, 5);
[...]
g_free (str);
donde [...] es algún código nuestro que procesa str de alguna manera
en este ejemplo, g_new0 es equivalente a:
str = g_malloc0 (sizeof (GString) * 5)
pero más fácil de leer y más cercano a orientación a objetos.
Tambien existen las funciones g_realloc, g_realloc0,
g_renew y g_renew0, que son para reubicar memoria y
retornan un puntero a un nuevo espacio de memoria, su equivalente es realloc
 en C estándar.
No podemos terminar de ver gestión de memoria dinámica sin
alex
gpoo: Una pregunta.
gpoo
ok
alex
<L00PeR> me llevo preguntando desde la anterior charlar de toma de
 contacto con Glib si el utilizar, por despiste, pongamos por ejemplo,
 malloc en vez de g_malloc influirá en algo al programa? puede llevar a
 error (exceptuando el "error de llevar una consistencia en el propio código"...
 no se si me he explicado bien... 
gpoo
veamos
tu preguntas:
que pasa si uso malloc en vez de g_malloc? ocurrirá algún error?
así es como lo entiendo
pues bien
es muy probable que no suceda nada
no mientras trabajes en la misma plataforma
g_malloc está hecha para ser tu aplicación portable
es decir, que se compile sin problemas en Linux, Solaris, HP/UX, etc.
hasta Windows
alex
<fxn> por que dice que g_malloc es como malloc pero completamente
 portable? malloc ya es completamente portable.
gpoo
fxn: si lo pensamos en términos de POSIX, sí
pero acá va la otra diferencia, que no alcancé a mencionar en la primera
 respuesta
y es que, g_malloc nos avisa si existen problemas
si bien es cierto, retornará NULL
es bien educado y en caso de error dirá:
g_error ("%s: failed to allocate %lu bytes", G_STRLOC, n_bytes);
es decir, detendrá la ejecución de nuestro programa 
y podremos determinar de mejor manera donde está el problema
de la forma usual en C, que sucede?
que la aplicación sigue su curso
y luego se comporta de una manera errática
o bien se cae en un lugar completamente distinto
y podemos pasar horas y horas tratando de seguirle la pista
sin mucho éxito
no sé si esto aclara la película
alex
<fxn> en ANSI C uno ha de mirar el tipo de retorno o picar un wrapper
 que lo haga por ti
<fxn> queria decir que hay que mirar si el puntero devuelto es NULL
 o no
gpoo
ok
pero no siempre evitarás los descuidos
y es mucho más robusto cuando las funciones te ayudan en ello
además
con las funciones g_malloc es posible obtener estadísticas de uso de memoria

si es que se define MEM_PROFILE o MEM_CHECK 
que lo veremos un poco más adelante
por otro lado
glib provee una interfaz
que permite ahorros de memoeria si tienes muchos bloques de siempre son
 del mismo tamaño
y los marca como ALLOC_ONLY si fuera necesario
facilitando la depuración
en resumen
son mucho mas seguras
te garantizan el mismo funcionamiento de tu aplicacion en todas las plataformas

te proveen facilidades como programador
puedes realizar profiling
que se pueden crear wrappers sobre malloc/free, es cierto
pero con glib, eso ya no se hace necesario
y es estándar en GNOME
más preguntas?
alex
no, por el momento ninguna.
gpoo
veamos... 
decía:
No podemos terminar de ver gestión de memoria sin
obtener un perfil del uso de memoria de nuestro programa.
Ya que hemos indicado que es importante adminstrar
bien la memoria para evitar "memory leaks".
Para obtener un perfil, emplearemos la función:
void g_mem_profile (void);
que en una de las respuestas ya mencioné
La cual nos entregará un resumen del uso de memoria.
Sin embargo, para que funcione como se espera, debe
inicializarse una tabla de perfiles de memoria, que,
afortunadamente, glib nos provee y es una variable:
extern GMemVTable *glib_mem_profiler_table;
que es una estructura
cuyo contenido en estos momentos no nos importa mayormente
mas que su finalidad
esta variable la debemos usar en conjunto con g_mem_set_vtable().
Así, nuestro programa quedará de la siguiente forma:
#include <glib.h>
int
main (int argc, char **argv)
{
       gchar *text;
       gint len = strlen ("Hola mundo") + 1;
 
       g_mem_set_vtable (glib_mem_profiler_table);
 
       text = g_malloc (len);
       g_snprintf (text, len, "Hola mundo");
       g_print ("%s\n", text);
       g_free (text);
 
        g_mem_profile ();
       
       return 0;
}
Antes de comenzar con las asignaciones de memoria,
hemos invocado a "g_mem_set_vtable" 
y le hemos dicho que queremos preparar el ambiente para obtener un perfil
 de la memoria empleada
es muy importante
al momento de usarla
que esta funcion debe ser llamada antes que cualquier otra función de glib

y antes de
finalizar, le pedimos un resumen.
co la función g_mem_profile
Lo compilamos
y si lo ejecutamos nos dará un resultado como el siguiente:
Hola mundo
GLib Memory statistics (successful operations):
 blocks of | allocated  | freed      | allocated  | freed      | n_bytes
   
  n_bytes  | n_times by | n_times by | n_times by | n_times by | remaining
 
           | malloc()   | free()     | realloc()  | realloc()  |      
     
===========|============|============|============|============|===========

        11 |          2 |          2 |          0 |          0 |      
   +0
        12 |          2 |          2 |          0 |          0 |      
   +0
        16 |          1 |          0 |          0 |          0 |      
  +16
        20 |          1 |          0 |          0 |          0 |      
  +20
        21 |          1 |          0 |          0 |          0 |      
  +21
        28 |          2 |          0 |          0 |          0 |      
  +56
        29 |          1 |          0 |          0 |          0 |      
  +29
        44 |          2 |          0 |          0 |          0 |      
  +88
GLib Memory statistics (failing operations):
 --- none ---
Total bytes: allocated=276, zero-initialized=20 (7.25%), freed=46 (16.67%),
 remaining=230
MemChunk bytes: allocated=3176, freed=0 (0.00%), remaining=3176
pues bien
básicamente eso es lo que glib nos provee para la gestión dinámica de memoria

alguna pregunta antes de comenzar a ver gestión de archivos?
alex
no, lo están comprendiendo todos muy bien. :-)
--- gnomero fija modos [#gnome-hispano +o telemaco]
--- telemaco fija modos [#gnome-hispano -o telemaco]
--- gnomero fija modos [#gnome-hispano +o diego]
gpoo
perfecto :-)
Gestión de archivos
lib ofrece algunas funciones útiles para el manejo de archivos y
además provee un mecanismo unificado de acceso a sockets, pipes
y archivos, para (una vez más) facilitar la portabilidad de las
aplicaciones
hmmm... sigo con problemas con la 'g'.  Estamos hablando de Glib (no lib)




GIOChannel es una capa de abstracción al cual podemos acceder
con los mismo métodos a sockets, pipes, archivos
y las funciones de GIOChannel comenzarán con el prefijo:
g_io_channel_
así por ejemplo, si queremos abrir un archivo con GIOChannel
creamos un canal con g_io_channel_new_file
cuyo formato es:
GIOChannel* g_io_channel_new_file (const gchar *filename, const gchar *mode,



 GError **error);
donde el primero parámetro es el nombre del archivo a abrir
el segunfo es el modo, que se emplean exactamente los mismos valores usados



 en fopen
y el último parámetro si deseamos realizar gestión de errores
es decir, cuando ocurra un error saber el detalle para tomar alguna acción



 o bien informar al usuario
de esto último, gestión de errores, se referirá Rodrigo Moya en la siguiente



 charla
así que por ahora, no empleremos ese parámetro, mas bien lo dejaremos en



 NULL
si abrimos un archivo para lectura
cremos un canal y luego podemos invocar alguna función como:
g_io_channel_read_chars
g_io_channel_read_line
g_io_channel_read_line_string
g_io_channel_read_to_end
g_io_channel_read_unichar
donde g_io_channel_read_chars lo podemos pensar como el clásico read ()




donde se indica una cantidad 'x' de bytes a leer
sin embargo, cuando trabajamos documentos de texto, normalmente nos interesa



 leer línea por línea
y allí nos sirven la 2da y 3ra función mencionada
la diferencia entre g_io_channel_read_line y g_io_channel_read_line_string




radica en que la primera usar un puntero a un gchar para devolver el valor




es decir gchar *
y en el segundo caso (g_io_channel_read_line_string) dejará el contenido




en una variable de tipo GString
que es lo que vio Roberto la semana pasada
y es el manejo de Strings que nos provee glib y que no existe de esa forma



 en C estándar.
antes de mostrar un ejemplo, les indicaré para que sirven las 2 últimas



 funciones
g_io_channel_read_to_end   lee hasta el final del archivo
y la última
g_io_channel_read_unichar nos permite leer en UTF8
partamos con un ejemplo sencillo:
#include <glib.h>
int
main (int argc, char *argv[])
{
       GIOChannel *io;
       gchar *buffer;
       gsize len;
 
       if (argc != 2) {
               g_printerr ("Uso: giochannel archivo\n");
               return -1;
       }
 
       io = g_io_channel_new_file (argv[1], "r", NULL);
 
       if (io != NULL) {
               while (g_io_channel_read_line (io, &buffer, &len,



 NULL, NULL) == G_IO_STATUS_NORMAL) {
                      g_print ("%s", buffer);
                      g_free (buffer);
               }
               g_io_channel_close (io);
       } else {
               g_printerr ("No se pudo abrir el archivo %s\n", argv[1]);




       }
       return 0;
}
Lo que haces esta aplicación es abrir un archivo cualquier e imprimir su



 contenido por pantalla
es decir, si el programa lo compilamos como giochannel
$ ./giochannel archivo.txt
nos imprimirá el contenido del archivo
analicemos paso a paso
io = g_io_channel_new_file (argv[1], "r", NULL);
creamos el canal, cuyo nombre de archivo lo ha dado el usuario y viene



 en argv[1]
lo hemos abierto en modo lectura ("r")
y le hemos indicado que no queremos una gestión de error mas fina
eso no quiere decir que no podamos (y no debamos) verificar errores
si la llamada es exitosa, nos devolvera un nuedo GIOChannel en io
de lo contrario, retornará NULL
y es lo que verificamos en:
if (io != NULL) {
es decir, si el archivo lo podemos leer, entonces...
le pedimos que lea línea a línea
el formato de g_io_channel_read_line es:
GIOStatus   g_io_channel_read_line          (GIOChannel *channel,
                                             gchar **str_return,
                                             gsize *length,
                                             gsize *terminator_pos,
                                             GError **error);
donde nosotros hemos ocupado solo algunos y el resto lo hemos dejado NULL




veamos:
      while (g_io_channel_read_line (io, &buffer, &len, NULL, NULL)



 == G_IO_STATUS_NORMAL) {
le hemos entregado el canal "io", y le hemos pasado el buffer 
es alli donde quedará alojado el contenido del la línea
además, le pasamos por referencia "len", para que nos indique cuantos bytes


 pudo leer
pudo haber sido NULL, si no nos interesase
el siguiente parametro, terminator_pos, es para que la función nos informe

 en que posicion se encuentra almacenada la última posicia
o mejor dicho, el terminado de línea
y el último, si queremos gestión de errores
la función nos retorna un valor GIOStatus
y puede ser uno de los siguientes:
  G_IO_STATUS_ERROR,
  G_IO_STATUS_NORMAL,
  G_IO_STATUS_EOF,
  G_IO_STATUS_AGAIN
donde G_IO_STATUS_NORMAL es sinónimo de éxito
entonces nuestro ciclo while se puede leer como:
"mientras pueda seguir leyendo una línea desde IO, haga"
hay que notar que:
declaramos buffer como gchar *buffer
y el parámetro que espera la función es gchar **
por lo tanto, lo hemos pasado como &buffer
si quisieramos usar GIOChannel combinado con popen
ya no podriamos usar la función g_io_channel_new_file
emplearemos entonces g_io_channel_unix_new ()
y el resto, funcionará de la misma forma
si quisieramos leer un socket, igual
usaremos g_io_channel_unix_new () y el resto de las funciones serán las

 mismas
veamos como es la forma de llamarlos
el formato es:
GIOChannel* g_io_channel_unix_new           (int fd);
debemos pasarle un descriptor de archivo (fd: file descriptor)
entonces:
diego
hola
gpoo
   FILE *pfd;  /* (popen file descriptor) */
luego:
   pfd = popen ("ls -l", "r");
   io = g_io_channel_unix_new (fileno (pfd));
etc.
en este caso, usamos fileno para obtener el descriptor del archivo desde
 un FILE *
de la misma forma se realiza con un socket
por ejemplo, si constuimos un cliente TCP
tendremos algo así como:
int 
main (int argc, char **argv)
{
    int sock;
[...]
    if( (sock = socket( PF_INET, SOCK_STREAM, 0 )) == -1 ) {
         /* mensaje de error porque no se creo bien el socket */
    }
   io = g_io_channel_unix_new (sock);
y de esta forma ya tenemos un GIOChannel a partir de un socket
es decir, ya hemos visto como se relacion un pipe, un socket y un archivo

que sucede si por algùn motivo debemos abrir un archivo con fopen?
es decir, que sucede si estamos obligados a usar FILE *
en ese caso:
usamos lo mismo que con popen
   FILE *fd;
realizamos el fopen
y luego:
   io = g_io_channel_unix_new (fileno (fd));
otra situación es cuando queremos crear archivos temporales
por ejemplo, cuando nuestra aplicación tiene la opción de "guardar"
guardamos todo lo que está en memoria a un archivo temporal
si es exitoso, renombramos el archivo temporal al nombre que ya existía

garantizando que no perderemos datos
para crear un archivo temporal
usaremos g_mkstemp
int         g_mkstemp                       (char *tmpl);
que funciona igual que mkstemp ()
esta función nos devuelte un int, es decir, el número del descriptor de
 archivo
por ejemplo:
   int fd;
   fd = g_mkstemp ("my_archivo-XXXXXX");
   io = g_io_channel_unix_new (fd);
y ya tenemos todo bajo GIOChannel
aunque estamos viendo GIOChannel
hare un alcance respecto a g_mkstemp
la explicacion para tener g_mkstemp y no usar directamente mkstemp es portabilidad

en donde exista mkstemp, glib lo usará
en donde no, lo proveerá
así nuestra aplicación podrá funcionar en cualquier plataforma donde exista
 glib
alguna pregunta?
alex
no :-)
gpoo
pues bien
llevamos 1 hora y 30 minutos
así que antes de terminar les mencionaré 
algunas funciones utiles, que no son parte de GIOChannel
pero seguramente los usaran en el futuro
g_file_get_contents ()
permite leer el contenido *completo* de un archivo y dejarlo en una variable

esto es util se queremos un editor de texto sencillo
o si sabemos que los archivos de todas formas lo necesitamos en memoria
 o bien son pequeños
eso nos evita iteraciones y lineas de codigo
g_file_test ()
nos permite verificar el tipo de un archivo
lo que normalmente hariamos con fstat o stat
así podemos saber si el archivo en cuestión es un enlace simbólico, si
 existe, si es ejecutable, directorio, etc.
y las funciones para manejar directorios: g_dir_open, g_dir_read_name
que sus nombres son bastante descriptivos.
mas pueden revisarlo en la documentación de Glib
pues bien
la próxima charla Rodrigo Moya les enseñará como crear programas con plugins
 (extensiones)
gestión de errores
y como trabajar con hilos (threads)
eso es todo :-)
--- alex fija modos [#gnome-hispano -m]
alex
Excelente charla German!
ponto
plas plas plas plas
plas plas plas plas
alex
plas plas plas plas plas plas plas plas plas plas plas plas plas plas plas
 
unimauro
clap, clap clap, clap clap, clap clap, clap clap, clap clap, clap clap,
 clap clap, clap clap, clap clap, clap clap, clap clap, clap clap, clap
 clap, clap clap, clap clap, clap clap, clap clap, clap clap, clap clap,
 clap clap, clap clap, clap clap, clap 
alex
plas plas plas plas plas plas plas plas plas plas plas plas plas plas plas
 
migraine
bien bien ;-) Germán
alex
plas plas plas plas plas plas plas plas plas plas plas plas plas plas plas
 
unimauro
Buena la charla 
nahoo
fenomenal
Ferdy
mu guena si señor
ton
muy buena, plas plas plas plas
Ferdy
felicidades y gracias
ponto
muy bien dada y muy claro todo lo que has dicho
gpoo
ya me estaba asustando, donde nadie preguntaba
:-)
Dragut^^
xDD
JulioC
bravo!!
Dragut^^
tá curra la verdad