Barra laterale

programmazione:c:creare_librerie

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: 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.

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 <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 .

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 <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.

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

programmazione/c/creare_librerie.txt · Ultima modifica: 18/04/2018 - 15:49 (modifica esterna)