Mensajes de salida

Mensajes de salida.

GLib implementa una serie de funciones para el envío de mensajes al exterior del programa. Al igual que en el ANSI C se dispone de la mítica función printf (), GLib posee una función que se asemeja en todo menos en el nombre; esta función es gprint () y su prototipo de función es:

void g_print (const gchar *format, ...);

Como se puede ver, el uso de g_print () es idéntico al de printf (). Es una función que recibe como parámetros un formato y las variables que se usan dentro de las características del formato, al igual que printf ().

Ejemplo 6-2.1. Mensajes de salida.

   1 /* ejemplo del uso de g_print */
   2 #include <glib.h>
   3 int
   4 main (int argc, char *argv[])
   5 {
   6         gint numero = 1;
   7         gchar *palabra = "hola";
   8         g_print ("glib te saluda : %s\nglib te dice un número : %d\n", palabra,
   9                  numero);
  10         return 0;
  11 }

Además, GLib provee de un método para dar formato a todos los mensajes de salida que se emitan con la función g_print (), por lo que se podría decir que, cada vez que se envía un mensaje con formato a g_print (), éste debe salir por pantalla con algún tipo de mensaje adicional. Esto es debido a la función g_set_print_handler (), que tiene como parámetros un puntero a función. Esta función, que es representada por el puntero a función, será definida por el programador y, con ella, se podrá decir qué mensaje adicional queremos que se vea. Para entenderlo mejor, se mostrará a continuación la sinopsis de esta función, así como un ejemplo.

GPrintFunc g_set_print_handler (GPrintFunc funcion);

Esta es la sinopsis de la función g_set_print_handler (), que recibirá como parámetro un puntero a función. Esta función podrá tener el siguiente aspecto

void (* GPrintFunc) (const gchar * cadena );

Éste sería el aspecto que ha de tener la función que se le pasará como parámetro a g_set_print_handler. El siguiente ejemplo ha sido concebido con la intención de facilitar su comprensión.

Ejemplo 6-2.2. Personalización de mensajes de salida.

   1 /* ejemplo de cómo añadir información a los mensajes de g_print */
   2 #include <glib.h>
   3 
   4 void funcion_personal (const gchar *cadena);
   5 
   6 /* funcion_personal es la función que le pasaremos a
   7    g_set_print_handler como parámetro */
   8 void
   9 funcion_personal (const gchar *cadena)
  10 {
  11         printf ("** Soy tu programa y te digo ** ");
  12         printf (cadena);
  13 }
  14 
  15 int
  16 main ()
  17 {
  18         /* g_print normal */
  19         g_print ("Soy un mensaje normal de salida\n");
  20 
  21         /* g_print personalizado */
  22         g_set_print_handler (funcion_personal);
  23         g_print ("mensaje de salida personalizado\n");
  24         return 0;
  25 }

GLib también provee de una función para comunicar mensajes de salida por la salida de errores estándar (stderr). Para el envío de mensajes existe la función g_printerr que funciona exactamente igual a g_print. Existe también una función como g_set_print_handler, llamada g_set_printerr_handler que funciona igual que la anterior.

Se puede ver, en conclusión, que el formateo personalizado de los mensajes de salida en GLib es bastante simple.

Funciones de depuración.

La depuración de un programa siempre es un trasiego por el cual todo programador, ya sea principiante o avanzado, ha de pasar. Y para hacer la vida más fácil al programador de GNOME, GLib nos ofrece una serie de funciones que harán más sencilla la depuración del código.

Estas funciones son útiles para comprobar que se cumple una serie de condiciones lógicas delimitadas por el programador. En caso de no ser cumplida, GLib emitirá un mensaje por consola, explicando que la condición que el programador ha puesto no se ha cumplido e indicando el archivo, la línea y la función en las que se ha roto la condición.

#define g_assert ( expresion );

La primera función de este tipo que se abordará es g_assert. Esta función recibe como parámetro un enunciado lógico y comprueba su validez. Para ejemplificar su uso, se expondrá un caso en el que a una función se le pase un puntero. Y se pondrá g_assert (ptr != NULL) para certificar que ese caso no se dé nunca. De hecho, si se diese tal caso, la función g_assert comprobaría que esa expresión no es válida y mandaría interrumpir la ejecución del programa. Para tener más claro este concepto, obsérvese la ejecución del siguiente ejemplo:

Ejemplo 6-2.3. Depuración de programas con g_assert.

   1 /* ejemplo del uso de g_assert */
   2 #include <glib.h>
   3 void
   4 escribe_linea (gchar *cadena)
   5 {
   6         /* si a la función se le pasa un puntero a NULL
   7            el programa cesara en la ejecución */
   8         g_assert (cadena != NULL);
   9         g_print ("%s\n", cadena);
  10 }
  11 
  12 int
  13 main ()
  14 {
  15         gchar *cadena = "esto es una cadena";
  16         gchar *no_es_cadena = NULL;
  17         /* como no se pasa un NULL sino una cadena, se escribirá
  18            la cadena "esto es una cadena" por pantalla */
  19         escribe_linea (cadena);
  20         /* como se le pasa un NULL el assert de la función
  21            escribe_linea surtirá efecto */
  22         escribe_linea (no_es_cadena);
  23         return 0;
  24 }

Si se compila este programa, en la salida del mismo, se podrá observar que cuando entra por segunda vez en la función escribe_linea, cesa la ejecución del programa. Además, por la consola saldrá un mensaje similar al siguiente.

        esto es una cadena

        ** ERROR **: file ejemplo.c: line 9 (escribe_linea): assertion failed: (cadena != NULL)
        aborting...

El mensaje muestra por pantalla información del archivo, de la línea y de la función en las que se viola la condición por la cual g_assert ha parado la ejecución. También indica qué expresión se ha incumplido, ya que pudiese darse el caso de tener varios g_assert en una misma función.

Quizá un método tan drástico como parar la ejecución del programa no sea lo más adecuado. Es probable que sólo se busque algo que termine la ejecución de la función y avise que una condición no ha sido cumplida. Para estos casos existen varias macros que facilitarán mucho la vida de los programadores. Estas macros son:

#define g_return_if_fail ()( expresion );

#define g_return_val_if_fail ()( expresion , val );

Ambas macros son similares en su funcionamiento, ya que comprueban si la expresión lógica que se les pasa como primer parámetro es verdadera o falsa. En el caso de ser falsa, aparecerá por consola un mensaje explicando que esa condición no ha sido cumplida. Esto es muy parecido a lo que hacía g_assert () la diferencia estriba en que estas dos funciones no finalizan la ejecución del programa, sino que simplemente hacen un retorno dentro de la propia función y, en consecuencia, el resto del programa seguirá ejecutándose.

Como previamente se mencionó, estas macros son similares. La diferencia radica en que la segunda, g_return_val_if_fail, hace que la función devuelva el valor que se le pasa como segundo parámetro.

Obviamente, g_return_if_fail se usará en funciones que no devuelvan ningún valor y g_return_val_if_fail en funciones que devuelvan algún valor. Para entender mejor estas funciones obsérvese el siguiente ejemplo.

Ejemplo 6-2.4. Depuración de programas con g_return_if_fail y g_return_val_if_fail.

   1 /* ejemplo del uso de g_return_if_fail y f_return_val_if_fail */
   2 #include <glib.h>
   3 
   4 void
   5 escribe_linea (gchar *cadena)
   6 {
   7         g_return_if_fail (cadena != NULL);
   8         g_print ("%s\n", cadena);
   9 }
  10 
  11 /* escribe línea 2 devolverá
  12    TRUE si la ha escrito bien
  13    FALSE si incumple la expresión */
  14 gboolean
  15 escribe_linea2 (gchar *cadena)
  16 {
  17         g_return_val_if_fail (cadena != NULL, FALSE);
  18         g_print ("%s\n", cadena);
  19         return  TRUE;
  20 }
  21 
  22 int
  23 main ()
  24 {
  25         gchar *cadena = "esto es una cadena";
  26         gchar *no_es_cadena = NULL;
  27         gboolean resultado ;
  28         escribe_linea (cadena);
  29         resultado = escribe_linea2 (cadena);
  30         if (resultado == TRUE)
  31                 g_print ("\n-- escribe_linea2 ha devuelto TRUE --\n");
  32         escribe_linea (no_es_cadena);
  33         resultado = escribe_linea2 (no_es_cadena);
  34         if (resultado == FALSE)
  35                 g_print ("\n-- escribe_linea2 ha devuelto FALSE --\n");
  36         return 0;
  37 }

Y, por consiguiente, la salida de este programa será similar a la siguiente:

esto es una cadena
esto es una cadena

-- escribe_linea2 ha devuelto TRUE --

** (process:1054): CRITICAL **: escribe_linea: assertion `cadena != NULL' failed

** (process:1054): CRITICAL **: escribe_linea2: assertion `cadena != NULL' failed

-- escribe_linea2 ha devuelto FALSE --

Como se puede ver, su ejecución también ofrece información útil, como el identificador del proceso al cual pertenece el mensaje y al igual que en el caso de la función g_assert, entrega como parte de la salida, la función en la cual no se ha cumplido la expresión y la condición que se viola, salvo que a diferencia de g_assert, esta vez no detiene la ejecución del programa.

Estas son las funciones para depuración que se pueden encontrar dentro de la biblioteca GLib. Aunque no son las únicas, sí son las más comunes.

Funciones de registro.

GLib tiene cuatro macros básicas para el tratamiento mensajes, por niveles de importancia. Estas macros permiten al programador realizar un registro del recorrido del programa especificando el nivel de importancia de los sucesos acontecidos dentro del mismo.

Estas cuatro macros reciben parámetros de la misma forma que lo hacían g_print o printf. De este modo, no sólo se tendrá un registro por niveles, sino que, además, se podrá especificar un texto preformateado. De este modo, los mensajes pueden representar por pantalla el contenido de variables o cualquier otra información que nos pudiera ser útil. Las cuatro macros de las que estamos hablando son :

#define g_message (...);

#define g_warning (...);

#define g_critical (...);

#define g_error (...);

Como se puede observar, las cuatro macros tienen nombres bastante intuitivos. Se puede notar, por los nombres de las funciones, de que se encuentran ordenadas de menor a mayor importancia, siendo g_message menos importante que g_error. A continuación se explicará lo que hace cada una de ellas.

Para una mejor comprensión se muestra el siguiente ejemplo. En él se han definido cuatro funciones, cada una de las cuales emite un mensaje diferente. En este ejemplo no se hace que g_critical tenga capacidad de abortar el programa porque si no, no veríamos como funciona g_error.

Ejemplo 6-2.5. Uso de mensajes de registro.

   1 /* ejemplo del uso de g_warning, g_critical, ... */
   2 #include <glib.h>
   3 void
   4 funcion_con_mensaje (){
   5         g_message ("Yo soy solamente un mensaje de aviso\n");
   6 }
   7 void
   8 funcion_con_aviso (){
   9         g_warning ("Esto es un aviso, algo puede ir mal\n");
  10 }
  11 void
  12 funcion_con_aviso_critico (){
  13         g_critical ("Esto se pone difícil :( \n");
  14 }
  15 void
  16 funcion_con_un_error (){
  17         g_error ("3, 2, 1... acabo de morirme. RIP\n");
  18 }
  19 main (){
  20         funcion_con_mensaje ();
  21         funcion_con_aviso ();
  22         funcion_con_aviso_critico ();
  23         funcion_con_un_error ();
  24 }

Aunque este ejemplo no tiene mucha funcionalidad, sirve para ver en acción a las cuatro macros en un mismo programa y da la posibilidad de contemplar en la salida de este programa los mensajes que envía a la consola. Si se ejecuta el ejemplo su salida sería:

** Message: Yo soy solamente un mensaje de aviso


** (process:13070): WARNING **: Esto es un aviso, algo puede ir mal


** (process:13070): CRITICAL **: Esto se pone difícil :( 


** ERROR **: 3, 2, 1... acabo de morirme. RIP

aborting...
Aborted (core dumped)

Como se puede ver, todas las funciones y macros que se han mostrado en este apartado serán especialmente útiles a la hora de realizar un programa y posibilitarán una vía estandarizada y muy flexible para la emisión de información al exterior. Facilitando la corrección del código y haciendo más facil la tarea del programador.


Volver a: GLib