====== Creare librerie in C ======
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 .
===== Creare file oggetto .o =====
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
===== Librerie statiche .a =====
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'' .
===== Librerie dinamiche (shared library) .so =====
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.\\
==== Creiamo una libreria dinamica ====
Prendendo in esempio questo codice: [[programmazione:c:creare_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.//
===== Compilare e linkare i programmi alla nostra libreria =====
Immagginiamo di avere 2 file che compongono la nostra libreria ''keys.h'' e ''keys.c'' così composti:
**keys.h :**
#include
#include
#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
#include
#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
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** .
===== Caricare una libreria dinamica a runtime =====
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 '' ''.\\
Riferendoci sempre a [[programmazione:c:creare_semplice_parser_per_file_configurazione]] ecco come dovrebbe essere modificato il codice del main:
#include
#include
#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.
==== Compilare eseguibile e lincarlo alla libreria ====
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'')
=== Un ipotetico makefile ===
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