6.1 Arquivos em formato Objeto e Executável
6.1.6 Bibliotecas Dinâmicas
Apesar de economizarem memória quando comparadas com as bibliotecas estáticas, pode ocorrer que algumas bibliotecas com- partilhadas sejam incluídas no espaço de memória virtual de um processo, mas que sejam pouco utilizadas ao longo da execução. Isto também acarreta gasto desnecessário de memória.
Em ambientes onde o recurso memória é muito escasso (como por exemplo em um computador com pouca memória), pode ser interessante exigir que somente as bibliotecas que serão efetivamente usadas sejam carregadas na memória, e que isto ocorra de acordo com as necessidades impostas pelo programador. Por exemplo, se o programa precisar de uma biblioteca no início e no fim da execução do programa, é razoável carregá-la no início, liberá-la durante a execução do programa e talvez re-carregá-la ao final para terminar o programa.
As bibliotecas que se comportam desta forma são chamadas de bibliotecas dinâmicas (Dynamic Link Libraries - DLLs), e para usá-las é necessário um esforço maior do programador, uma vez que o programa principal deve sofrer alterações significativas. Como habitual, vamos exemplificar para uso na linguagemCem Linux.
A idéia geral é criar ferramentas que permitem que o programador possa: 1. incluir a biblioteca dinâmica em qualquer momento da execução; 2. liberar a biblioteca dinâmica em qualquer momento da execução; 3. executar as funções da biblioteca dinâmica incluída.
Em linux, foi criada uma API para executar esta tarefa, que é chamada de interface para o carregador dinâmico. As funções básicas desta API sãodlclose,dlerror,dlopenedlsym.
A interface completa está documentada nas man pages (por exemploman dlclose), e aqui iremos apresentar um exemplo de como utilizá-las. Os passos necessários são:
1. Deve ser incluído o cabeçalho<dlfcn.h>, que contém os protótipos das funções dlopen, dlclose e dlsym.
2. A biblioteca deve ser aberta explicitamente através da função dlopen, que irá retornar um handle para esta biblioteca:
(...)
void *handle;
char *pathLib="/home/.../libMyDynamicLib.so"; (...)
handle = dlopen ( pathLib, RTLD_LAZY ); (...)
3. A função desejada na biblioteca deve ser associada a um ponteiro para função como exemplificado a seguir:
(...)
void (*localSym)(parametros da funcao na bib); (...)
localSym = dlsym (handle, "nome da funcao na bib");
4. O símbololocalSymagora é um sinônimo da função indicada na biblioteca. Para executar a função da biblioteca, basta usarlocalSymcom os mesmos parâmetros da função.
localSym ( parâmetros ... );
6.1. Arquivos em formato Objeto e Executável 109
dlclose ( handle );
Suponha que temos dois arquivos fonte (a.c e b.c) com os quais deve ser criada a biblioteca libMyDynamicLib.so. Para criar a biblioteca e para gerar o programa executável, deve ser digitada a seguinte sequência de comandos:
> gcc -fPIC -o a.o -c a.c > gcc -fPIC -o b.o -c b.c
> ld -shared -o libMyDynamic.so a.o b.o > gcc -o main main.c -L. -lMyDynamic -ldl
Observe que a única diferença com relação ao processo de compilação de bibliotecas compartilhadas é a inclusão da biblio- teca -ldl", que contém a implementação das funções da interface para o carregador dinâmico.
Este tópico está bem documentado em dois artigos disponíveis na internet, em especial em [42, 2] (que devem ser lidos).
1
#include <stdio.h>
23
void a (char* s) {
4
printf ("(a): %s\n", s);
5}
Algoritmo 58: Arquivo a.c
1
#include <stdio.h>
2 3void b (char* s) {
4printf ("(b): %s\n", s);
5}
Algoritmo 59: Arquivo b.c
Como exemplo, considere os algoritmos 58, 59, e 60.
O laço contido entre as linhas 13 e 16 espera que o usuário digite a letraaoub. As linhas 18 e 19 colocam no vetorso caminho completo (desde a raiz do sistema de arquivos) para o arquivo onde está a biblioteca que iremos incluir dinamicamente. Para tal é utilizada a função getenv (get environment variable), que coloca um ponteiro com o string da variável de ambiente HOME na variável local path. Mais adiante veremos que esta variável de ambiente (na verdade todas as variáveis de ambiente) está inserida no arquivo executável.
As linhas 21 até 26 abrem a biblioteca dinâmica e verificam se houve algum erro (por exemplo, biblioteca não encontrada). Se tudo correr bem, a variável error seráNULL, ou seja, zero. A opção RTLD_LAZYpode ser entendida como indicador para que a biblioteca seja carregada “preguiçosamente”, ou seja, quando alguma das funções contidas nela for utilizada. Observe que a biblioteca é aberta e associada a uma variável (handle). Daquele ponto em diante, todas as vezes que for utilizada a variável
handle, estaremos fazendo referência ao arquivo biblioteca aberto.
As linhas 28 até 31 indicam qual das duas funções contida na biblioteca deve ser utilizada. O comandofuncao = dlsym(handle, "a");pode ser lida da seguinte forma: Associe a funçãoa()contida na biblioteca dinâmica indicada emhandleà variável
funcao. Isto se faz basicamente copiando o endereço da funçãoapara a variávelfuncao.
A execução está na linha 33. Observe que aqui o conteúdo da variávelfuncaoé ou o endereço da funçãoaou o endereço da funçãob. Como as duas funções tem a mesma sequência de parâmetros, não ocorrerá nenhum problema.
Por fim, a linha 35 fecha a biblioteca.
A forma de gerar o programa executável, e o resultado obtido pela execução do programa é a seguinte:
> gcc -fPIC -o a.o -c a.c > gcc -fPIC -o b.o -c b.c
> ld -shared -o libMyDynamicLib.so a.o b.o > gcc -o main main.c -L. -ldl
> ./main
Digite (a) para uma bib e (b) para a outra a
1
#include <stdlib.h>
2#include <dlfcn.h>
34
int main ( int argc, char** argv ){
5char opcao;
6void* handle;
7void* (*funcao)(char*);
8char* error;
9char* nomeBib="/usr/lib/libMyDynamicLib.so";
10char* path;
11char s[100];
12 13do{
14
printf("Digite (a) para uma função e (b) para a outra \n");
15scanf ("%c", &opcao );
16
} while (opcao!=’a’ && opcao!=’b’);
1718
path = getenv ("HOME");
19
sprintf(s, "%s%s", path, nomeBib);
2021
handle = dlopen(s, RTLD_LAZY);
22error = dlerror();
23if ( error ){
24printf("Erro ao abrir %s", s );
25exit (1);
26}
27 28if ( opcao == ’a’ )
29
funcao = dlsym(handle, "a");
30
else
31funcao = dlsym(handle, "b");
32 33funcao ("texto\n");
34 35dlclose (handle);
36}
Algoritmo 60: Arquivo main.c
> ./main
Digite (a) para uma bib e (b) para a outra b
(b): texto >
Como pode ser visto, a geração da biblioteca (ld ...) é igual ao que ocorre em bibliotecas compartilhadas. Porém, há algo de novo na linhagcc -o main main.c -L. -ldl. A opção-ldl, diz para incluir estaticamente a bibliotecalibdl.a. Esta biblioteca é que contém as funçõesdlopen,dlclose,dlsyme dlerror. Ela contém muitas outras funções do tipodl*, mas o nosso exemplo só usou estas.
Para maiores detalhes de uso de bibliotecas, consulte[2, 42]. A terceira parte do presente texto mostra como estas bibliotecas se comportam em tempo de execução.
6.1. Arquivos em formato Objeto e Executável 111