<<
^
>>

Programação C simplificada com glib e outros ajudantes

Visão geral da biblioteca glib

A biblioteca glib costuma ser sempre associada a programas GNOME e Gtk+. Isso se deve ao fato de a biblioteca Gtk+ ser altamente dependente e grande usuária dos recursos da glib.

É a glib, na verdade, quem fornece atualmente os recursos de orientação a objetos, sinais, temporizadores e looping de eventos. Além disso ela fornece uma enorme gama de funcionalidades que simplificam a vida do programador.

A glib fornece, por exemplo, implementações prontas e bem-testadas de listas encadeadas, tabelas hash e vários outros tipos de dados avançados além de funções e macros que facilitam a operação com tipos mais "simples", como strings, números inteiros e de ponto flutuante.

Como aprender

A glib e as outras bibliotecas que iremos estudar fazem uso pesado de convenções que são extremamente úteis para facilitar o entendimento do uso delas. Vamos dar uma olhada nessas convenções. Antes de mais nada, temos convenções mais gerais como:

Um leitor atento provavelmente notará uma outra convenção muito importante em aplicação nos exemplos acima: todos os nomes são compostos de NAMESPACE_MODULO_ALGUMACOISA no primeiro caso, NamespaceMóduloAlgumacoisa, no segundo e namespace_módulo_algumacoisa, no caso de funções.

Uma outra convenção importante que foi adotada nessas bibliotecas é a do uso do const em retornos de funções que retornam strings e outros tipos de dados que costumam ser alocados dinamicamente e que, nesses casos, não devem ser liberados manualmente pelo programador. Disso se tira uma regra importante: toda string retornada por uma função da glib ou da gtk+ que não seja const deve ser liberada com g_free (). É bom, mesmo assim, prestar atenção à referência da API da biblioteca em questão.

Existem ferramentas que ajudam e facilitam muito o trabalho com as bibliotecas relacionadas a Gtk+ e GLib. Uma delas é o Devhelp, que é um navegador de referência de API para desenvolvedores. Antes de continuar instale essa aplicação e se familiarize com seu uso. Aqui está uma foto de sua janela principal:

Screenshot do Devhelp

Você também pode encontrar as referências de API das bibliotecas que vamos usar no sítio do Gtk+.

Tipos de dados e macros: portabilidade em evidência

A glib fornece tipos de dados que, de certa forma, substituem os tipos básicos de C. Esses tipos não servem apenas para fazer o código ficar mais "glibeado", mas são de muita utilidade na questão da portabilidade. Você pode usar, por exemplo, um gint8 para ter um inteiro de 8 bits em todas as plataformas. Você pode também usar G_MAXDOUBLE para saber qual o valor máximo que um double pode alcançar em determinado sistema.

Além disso há outras facilidades como um tipo gpointer que substitui o açucar sintático necessário ao ponteiro genérico do C (void*). No campo das macros, a glib fornece facilidades usadas normalmente como TRUE, FALSE e até NULL. Além disso, facilidaes para saber em que sistema você está compilando o programa: G_OS_WIN ou G_OS_UNIX, por exemplo.

Podemos perceber que a glib tem uma grande preocupação com portabilidade. Portabilidade é uma coisa boa porque evita que fiquemos trancados em uma única plataforma. Por esses motivos, esse documento sugere e assume sempre o uso das facilidades fornecidas pela glib sempre que possível.

Você pode ler mais sobre tipos e macros da glib na seção GLib Fundamentals da referência de API no Devhelp ou online.

Um ajudante de compilação: pkg-config

Antes de seguirmos em frente, vamos ver alguma coisa sobre compilação para podermos testar nossos programas de teste.

Aqueles que já estão acostumados a compilar programas com o compilador gcc conhecem opções de comando como -I, -L e -l. Os dois primeiros são indicadores de onde se deve procurar arquivos cabeçalhos e bibliotecas e o último serve para pedir ao compilador que ligue (de to link) bibliotecas específicas ao binário final.

Existe um ajudante muito útil no caso das bibliotecas parentes de Gtk+: o pkg-config. Ele usa os arquivos guardados em /usr/lib/pkg-config/ que contêm informações necessárias para compilar as diversas bibliotecas. Vamos usá-lo, então, para compilar nossos programas.

Aos arquivos de cada biblioteca chamaremos "pacotes". Os pacotes que usaremos com frequência nesse curso serão glib-2.0, gtk+-2.0 e libglade-2.0. Alguns pacotes declaram dependências em outros pacotes. Isso significa que quando pedimos ao pkg-config para nos dar as informações do pacote gtk+-2.0 ele também nos dará informações do glib-2.0 automaticamente. Vejamos alguns exemplos.

Suponhamos que tenhamos digitado o seguinte programa e salvado em um arquivo hello.c:

#include <glib.h> int main () { g_print ("Oi, Mundo!\n"); return 0; }

Podemos compilá-lo com a seguinte linha de comando:

$ pkg-config --libs glib-2.0 -lglib-2.0 $ pkg-config --cflags glib-2.0 -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include $ gcc -o hello hello.c `pkg-config --libs --cflags glib-2.0` $ ./hello Oi, Mundo! $

Vamos analisar esse exemplo, então: os dois primeiros comandos são somente para demonstrar o que o comando exibe para cada opção passada. A terceira linha de comando (quinta linha de texto) é a que nos mais importa. Os que já têm experiência com programação shell saberão que os dois caracteres '`' dizem ao shell para pegar a saída padrão do comando que está entre eles e colocá-la em seu lugar.

Isso significa que passamos para o gcc as coordenadas para que ele localize o arquivo cabeçalho glib.h, que está em /usr/include/glib-2.0 (veja a saída do segundo comando) e pedimos a ele que ligue em nosso executável a biblioteca /usr/lib/libglib-2.0.so (saída do primeiro comando).

Gerenciamento de memória com GLib

Como já vimos no exemplo anterior, para usarmos as funções da glib incluímos o header glib.h. Você pode descobrir quais os nomes dos headers que precisa incluir para usar determinadas funções na documentação de referência da API da biblioteca que as fornece.

A biblioteca glib fornece funções para gerenciamento de memória com checagem de erro embutido, para evitar que cada projeto tenha de criar seus próprios encapsuladores ao redor das funções malloc (), realloc () e free ().

O uso das funções g_malloc (), g_realloc () e g_free () é muito semelhante ao uso de suas primas mais "incautas". Vejamos um exemplo simples:

void funcao () { gchar *nome; /* aloca uma string de 8 bytes */ nome = g_malloc (sizeof(gchar) * 8); g_strlcpy (nome, "Gustavo", 8); /* muda seu tamanho para 16 bytes */ nome = g_realloc (nome, sizeof(gchar) * 16); g_strlcat (nome, " Noronha", 16); printf ("Nome: %s\n", nome); g_free (nome); }

Além dessas, a glib fornece também uma função muito útil que é a g_new (), que serve para "instanciar" structs, alocar memória para elas. Supondo que tenhamos uma struct Pessoa, poderíamos alocar memória para uma struct desse tipo da seguinte forma:

struct Pessoa *p = g_new (struct Pessoa, 1);

Lidando com strings em GLib

Lidar com strings em C é, no mínimo, massante. Vejamos alguns exemplos de funções que facilitam a vida.

Vimos na seção anterior o uso de duas funções que lidam com string na glib: g_strlcat () e g_strlcpy (). Essas funções são semelhantes às funções strncat () e strncpy () da biblioteca padrão do C, adicionando funcionalidade de checagem de erros adicional. A glib fornece várias funções com essa característica, use o Devhelp e procure por elas! Vejamos agora algumas funções que poderão nos ser muito úteis e que não têm correspondente na biblioteca C padrão:

g_strdup_printf (): a função g_strdup_printf () nos ajuda a criar uma string da mesma forma como a função sprintf faria com uma diferença: o tamanho de memória necessário para que a string caiba é alocado automaticamente. Lembre-se de liberar a área de memória alocada com g_free () quando não precisar mais dela. Vejamos um exemplo de uso:

gchar * obter_mensagem_de_boas_vindas (gchar *nome) { gchar *mensagem; mensagem = g_strdup_printf ("Bem-vindo, %s!\n", nome); return mensagem; }

g_strplit (): essa função é uma utilíssima substituta para as complicadas strtok () e strstr (). Ela "quebra" a string em pedaços a partir de um delimitador qualquer retornando um vetor de strings (ou seja, um gchar**) que precisa ser desalocado com g_strfreev (). Vejamos um exemplo de uso:

/* Arranjo do arquivo: nome:idade:sexo */ gchar* obter_nome (gchar *linha_do_arquivo) { gchar **campos; gchar *nome; campos = g_strsplit (linha_do_arquivo, ":", 3); nome = g_strdup (campos[0]); g_strfreev (campos); return nome; }

É quase consenso na comunidade do Software Livre, talvez por herança da cultura Unix, que formatos e protocolos binários devem ser evitados ao máximo. Por isso mesmo coisas como gravar uma struct direto em um arquivo é desaconselhável e um arquivo texto bem-projetado deve ser quase sempre o formato ideal para guardar dados, por isso um bom conhecimento sobre como lidar com strings é não só aconselhável, mas quase obrigatório.

Veja mais funções como essas na documentação de referência.

Uso de Listas em GLib

Vejamos só mais um exemplo de como a GLib pode facilitar nossa vida quando programamos em C: criaremos uma lista. Isso vai ser útil quando lidarmos com a ComboBox do Gtk+.

Nada melhor que um exemplo para ilustrar o que precisamos saber:

void para_cada_string (gpointer string, gpointer dados) { printf ("string: %s\n", (gchar*)string); } void para_cada_inteiro (gpointer inteiro, gpointer dados) { printf ("inteiro: %d\n", GPOINTER_TO_INT(inteiro)); } void exemplo_de_lista () { GList *lista_de_strings = NULL; GList *lista_de_inteiros = NULL; lista_de_strings = g_list_append (lista_de_strings, "primeira"); lista_de_strings = g_list_append (lista_de_strings, "segunda"); g_list_foreach (lista_de_strings, para_cada_string, NULL); g_list_free (lista_de_strings); lista_de_inteiros = g_list_append (lista_de_inteiros, GINT_TO_POINTER (43)); lista_de_inteiros = g_list_append (lista_de_inteiros, GINT_TO_POINTER (14)); g_list_foreach (lista_de_inteiros, para_cada_inteiro, NULL); g_list_free (lista_de_inteiros); }

Há coisas importantes a serem notadas nesse exemplo. A variável que conterá a lista precisa ser NULL, por exemplo, se não problemas podem aparecer, já que, como vimos, não existe uma função que crie uma lista... já saímos diretamente adicionando coisas a ela. A função g_list_foreach () facilita muito o trabalho com a lista, assim como várias outras como a g_list_concat (), que serve para juntar duas listas e outras que podem ser vistas na documentação.

O uso das macros GINT_TO_POINTER e GPOINTER_TO_INT é, também, de grande importância. Lembre-se que a lista guarda um ponteiro como seu dado principal e, por isso, precisamos disso. No caso de strings e outras variáveis que são "ponteiros" por natureza, esse tipo de conversão não se faz necessária.

A GLib fornece inúmeros outros tipos de estruturas de dados que podem vir a ser muito úteis para o programador como tabelas hash, listas encadeadas simples (a GList é duplamente encadeada), árvores, vetores, e outras. Vale a pena conferir a referência da API.

<<
^
>>