Autore: Fabio Di Matteo
Ultima revisione: 03/07/2008
Per creare una qualsiasi libreria binaria si deve compilare in file oggetto
il codice che ci interessa quindi è doveroso fare una breve introduzione su cosa sono i file oggetto e la compilazione separata.
Il possiede una caratteristica utilissima nel lavoro di gruppo che è la compilazione separata. La compilazione separata permette di compilare singoli file sorgente in codice oggetto per poi mettere assieme il tutto non appena i “pezzi sono tutti finiti”.
E' utile se si pensa a programmi di grosse dimenzioni che necessitano di modifiche minimali per le quali sarebbe necessario ricompilare il progetto da capo .
In questo caso abbiamo un progetto di 4 file, il main.c
,lib1.c
,lib2.c
,lib3.c
:
gcc –c main.c –o main.o gcc –c lib1.c –o lib1.o gcc –c lib2.c –o lib2.o gcc –c lib3.c –o lib3.o
otteniamo in questo modo i corrispondenti file main.o
,lib1.o
,lib2.o
,lib3.o
, che possiamo compilare
con:
gcc main.o lib1.o lib2.o lib3.o –o project.bin
In soldoni una libreria statica serve per mettere a disposizione del nostro sorgente le istruzioni della stessa in fase di compilazione. Una volta compilato il nostro progetto la libreria non serve più perchè il suo contenuto verrà integrato dentro l'eseguibile del nostro programma.
Sempre riferendoci al caso di prima ecco i passi che ci servono per ottenere una libreria statica di nome libreria.a
:
gcc -c lib1.c lib2.c lib3.c ar -r libreria.a lib1.o lib2.o lib3.o
I programmi che useranno libreria.a
vengono compilati in questo modo:
gcc main.c libreria.a -o programma
N.B. come possiamo notare una libreria statica non è nient'altro che un archivio in formato ar di file oggetto.
Da notare inoltre che il file main.c
è messo come primo argomento di gcc
.
Le librerie dinamiche si differenziano da quelle statiche per il fatto che vengono caricate in memoria quando si avvia il programma che le usa, quindi possono essere condivise da più programmi.
Prendendo in esempio questo codice: Creare un semplice parser per file configurazione , creiamo la libreria dinamica a partire dal file keys.c
:
gcc -fpic -c keys.c gcc -shared -Wl,-soname,libkeys.so.1 -o libkeys.so.1.2.3 keys.o
La prima istruzione crea il codice oggetto del file keys.c
, la seconda
istruzione cre il file di libreria dinamica libkeys.so.1.2.3
.
N.B. -soname,libkeys.so.1
vuol dire che la libreria ha un nome principale (soname) che è libkeys.so.1
.
Il nome principale della libreria (soname) viene compilato anche nell'eseguibile che richiede la libreria, cosicchè quando ci sono versioni differenti della stessa libreria l'eseguibile che la richiede sa quale versione preferire.
Per un esempio più concreto e reale si continui a leggere il prossimo paragrafo.
Immagginiamo di avere 2 file che compongono la nostra libreria keys.h
e keys.c
così composti:
keys.h :
#include <stdio.h> #include <string.h> #define MAXKEY 50 #define MAXVALUE 50 #define MAXLINE 256 int wordcount(char* line); char* keyread(char *path, char* key); void keywrite(char *path, char* key, char* value); int keydel(char *path, char* key);
keys.c :
#include <stdio.h> #include <string.h> #define MAXKEY 50 #define MAXVALUE 50 #define MAXLINE 256 int wordcount(char* line); char* keyread(char *path, char* key); void keywrite(char *path, char* key, char* value); int keydel(char *path, char* key); char* keyread(char *path, char* key){ FILE *f=fopen(path, "r"); char valuetmp[MAXVALUE], line[MAXVALUE+MAXKEY], *value, key_now[MAXKEY] ; int i; while (feof(f)==0){ fgets(line,MAXVALUE+MAXKEY,f); if (line[0]!='#' && wordcount(line)==2){ sscanf(line, "%s %s\n",key_now, valuetmp); if (strcmp(key_now, key)==0){ value=malloc(sizeof(valuetmp)); strcpy(value,valuetmp); return value; } } } fclose(f); } void keywrite(char *path, char* key, char* value){ FILE *f=fopen(path, "r"); int i, pos=0, idxline=0 ; char valuetmp[MAXVALUE], line[MAXVALUE+MAXKEY], *nline[MAXVALUE+MAXKEY], key_now[MAXKEY] ; //get key's position while (feof(f)==0){ pos++; nline[pos-1]=malloc(MAXLINE); fgets(nline[pos-1],MAXLINE,f); if (nline[pos-1][0]!='#' && wordcount(nline[pos-1])==2){ sscanf(nline[pos-1], "%s %s\n",key_now, valuetmp); if (strcmp(key_now, key)==0){ idxline=pos-1; } } } fclose(f); //edit or append key nline[idxline]=malloc(sizeof(key)+sizeof(value)+1); if (idxline!=0){ //edit key sprintf(nline[idxline] ,"%s %s\n",key,value); FILE *f=fopen(path, "w"); for (i=0;i<=pos-1;i++) fputs(nline[i],f); fclose(f); }else{ //append new key FILE *f=fopen(path, "a"); sprintf(nline[pos-1] ,"%s %s\n",key,value);; fputs(nline[pos-1],f); fclose(f); } } int keydel(char *path, char* key){ FILE *f=fopen(path, "r"); int i, pos=0, idxline=0 ; char valuetmp[MAXVALUE], line[MAXVALUE+MAXKEY], *nline[MAXVALUE+MAXKEY], key_now[MAXKEY] ; //get key's position while (feof(f)==0){ pos++; nline[pos-1]=malloc(MAXLINE); fgets(nline[pos-1],MAXLINE,f); if (nline[pos-1][0]!='#' && wordcount(nline[pos-1])==2){ sscanf(nline[pos-1], "%s %s\n",key_now, valuetmp); if (strcmp(key_now, key)==0){ idxline=pos-1; } } } fclose(f); //delete key nline[idxline]=malloc(sizeof(key)+MAXVALUE+1); if (idxline!=0){ FILE *f=fopen(path, "w"); for (i=0;i<=pos-1;i++){ if (i!=idxline)fputs(nline[i],f); } fclose(f); }else{ return -1; } } int wordcount(char* line){ int i,count=0; for(i=0; i<strlen(line);i++){ if (isspace(line[i]) && !isspace(line[i+1]) ) count++; } return count; }
A partire da questi file creeremo la nostra libreria con il seguente makefile:
all: #Compila libreria contenuta keys.h keys.c gcc -fpic -c keys.h keys.c gcc -shared -Wl,-soname,libkeys.so.1 -o libkeys.so.1.2.3 keys.o install: #copiare libkeys.so.1.2.3 in /usr/local/lib e lanciare ldconfig
Dopo che si lancia il makefile verrà generato un file di nome libkeys.so.1.2.3
che è la nostra libreria dinamica.
Per compilare i programmi che usano la nostra libreria serviranno il file keys.h
(il nostro header) e il file libkeys.so.1.2.3
(la libreria appena compilata).
Copiare solo libkeys.so.1.2.3
in /usr/local/lib
o /usr/lib
e lanciare il comando ldconfig (da root) . Questo perchè il sistema (GNU/Linux) ha un elenco delle directory dove andare a trovare le librerie dynamiche, specificato nel file /etc/ld.so.conf
.
Il file keys.h
lo si lascerà invece nella directory del nostro programma.
Per compilare i programmi con la nostra libreria:
gcc -L. -I. /usr/local/lib/libkeys.so.1.2.3 -o main main.c keys.h
o anche soltanto:
gcc -L. -I. /usr/local/lib/libkeys.so.1 -o main main.c keys.h
in quanto ldconfig ha creato un link con il soname della libreria nella stessa directory.
N.B. In pratica per compilare un programma che usa una libreria, statica o dinamica che sia, c'è solo bisogno dell'header della libreria (il file delle intestazioni [dette anche prototipi]) e della libreria compilata .
Una libreria dinamica può essere carica a runtime dall'eseguibile che la richiede (caso proprio dei plugin delle applicazioni).
L'eseguibile tuttavia deve fare uso opportuno delle funzioni dlopen(), dlsym() e dlerror()
contenute in <dlfcn.h>
.
Riferendoci sempre a Creare un semplice parser per file configurazione ecco come dovrebbe essere modificato il codice del main:
#include <stdio.h> #include <dlfcn.h> #define PREF "preference.conf" int main(int *argc, char *argv[]){ /*Caricamento libreria dinamica a runtime*/ void *handle; /*le nostre 3 funzioni contenute nel sorgente della libreria, da notare che qui diventano puntatori a funzione*/ char* (*keyread)(char*, char*); void* (*keywrite)(char*, char*, char*); int (*keydel)(char*, char*); char *error; handle = dlopen ("./libkeys.so.1.2.3", RTLD_LAZY); if (!handle) { fprintf (stderr, "%s\n", dlerror()); exit(1); } dlerror(); /* Cancella ogni errore preesistente */ keyread = dlsym(handle, "keyread"); keywrite = dlsym(handle, "keywrite"); keydel = dlsym(handle, "keydel"); if ((error = dlerror()) != NULL) { fprintf (stderr, "%s\n", error); exit(1); } /*fine caricamento a runtime libreria dinamica*/ if (argc>2){ if (argv[1][1]=='r'){ char *valore=(*keyread)(PREF , argv[2]); printf("%s\n",(valore!=NULL ? valore:"0")) ; } if (argv[1][1]=='w'){ (*keywrite)(PREF,argv[2], argv[3] ); } if (argv[1][1]=='d'){ if ((*keydel)(PREF,argv[2])==-1) printf("key not present!\n") ; } }else{ printf("USAGE: keys -r or -w arg1 [arg2]\n "); } return 0; }
Il nome del file libreria è libkeys.so.1.2.3 e si trova nella stessa directory dell'eseguibile.
gcc -rdynamic -o main main.c -ldl
in questo modo l'eseguibile creato, main
,aprirà al momento opportuno la libreria. (la libreria non verra annoverata nell'output di ld main
)
Ecco un ipotetico makefile per compilare libreria e eseguibile d'uncolpo:
all: #Compila libreria a partire da keys.c gcc -fpic -c keys.c gcc -shared -Wl,-soname,libkeys.so.1 -o libkeys.so.1.2.3 keys.o #Compila l'eseguibile main gcc -rdynamic -o main main.c -ldl