Une bibliothèque peut être vue comme une collection de fonctions autonomes pouvant être partagées entre plusieurs programmes. L'intérêt de ce principe de séparation entre le programme principal et les fonctions est de permettre de faire l'évolution des fonctions sans forcer à la recompilation de l'ensemble et d'offrir une plus grande modularité dans la conception de l'application.
Considérons un fichier my_lib.c qui contiendra une fonction retournant le carré d'un nombre passé en paramètres.
int carre(int a)
{
return(a*a);
}
Nous allons ajouter un fichier d'entête my_lib.hqui va contenir les
différentes fonctions que nous allons déclarer utilisables par les programmes externes.
#ifndef __MY_LIB__H__
#define __MY_LIB__H__
#include <stdio.h>
extern double carre(double);
#endif
Pour transformer ce bout de programme en bibliothèque, il suffit de deux étapes de
compilation.
Puisque notre bibliothèque doit être partagée par plusieurs programmes en mémoire,
il n'est pas possible de créer une bibliothèque qui va charger ses données à des endroits
précisément définis. C'est le chargeur de programme qui doit décider de l'emplacement
de l'allocation mémoire de ces données propres à la bibliothèque.
C'est la but du commutateur -fpic. Cela aurait pu
également être -fPIC selon votre envie d'obtenir plus de code de debug.
gcc -c -Wall -Werror -fpic my_lib.c
Il nous faut transformer le programme objet obtenu à l'étape précédente en fichier
de bibliothèque partagée.
gcc -shared -o libmy_lib.so my_lib.o
Si l'on veut produire du versioning sur les bibliothèques, il faut rajouter une option
sur la ligne de compilation du genre
gcc -shared -Wl,-soname,libmy_lib.so.1 -o libmyl_lib.so.1.0 my_lib.o
L'option -Wl,-soname,libmy_lib.so.1 est passée au linker pour produire un
fichier de bibliothèque de numéro de version égal à 1 pour libmy_lib.so.1. L'option
-o est quant à elle passée à gcc pour produire un fichier nommé libmy_lib.so.1.0
correspondant à la version 1.0 de votre bibliothèque. Vous pouvez nommer les versions
comme vous le voulez, en respectant tout de même le fait que c'est normalement
version.revision[.micro] et que :
Généralement lorsque l'on veut utiliser une bibliothèque simplement, il va nous falloir effectuer l'étape d'édition des liens avec une référence à la bibliothèque en question. Commençons par écrire un fichier test.c qui va nous permettre de tester cette bibliothèque.
#include <stdio.h>
#include "my_lib.h"
int main(int argc, char **argv)
{
printf("Programme de test de la bibliotheque partagee\n");
printf("Carre de 8 : %lf\n", carre(8.5));
return(0);
}
Comme avec toutes les bibliothèques partagées, vous devez inclure le fichier
d'entête qui permettra au compilateur de retrouver les références aux différentes
fonctions de la bibliothèque que vous voulez utiliser.
Lors de l'exécution du programme :
./test
vous devriez obtenir ceci :
./test : error while loading shared libraries: libmy_lib.so: cannot
open shared object file: no such file or directory
Par défaut, le chargeur dynamique ld.so vérifie l'accès aux bibliothèques
selon le schéma :
./test: error while loading shared libraries: libmy_lib.so.1: cannot open shared object file: no such file or directory
c'est que vous n'avez pas le fichier de bibliothèque pointant sur la bonne version
du fichier. Pour déterminer ce dont vous avez besoin comme version de fichier, il
vous suffit de faire
include /etc/ld.so.conf.d/*.conf
Ce dossier contient simplement des fichiers précisant les chemins où les différentes
bibliothèques partagées se situent. Dans ce cas, crééz simplement un fichier du
genre sudo nano libmy_lib.conf dans le dossier /etc/ld.so.conf.d/
et dans le fichier spécifiez le chemin dans lequel se trouve votre bibliothèque.
La commande ldd permet de déterminer les bibliothèques partagées requises par un programme. Ainsi, pour GCC, nous obtenons
# ldd /usr/bin/gcc linux-vdso.so.1 (0x00007ffdfc7ac000) libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fa35dff2000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa35dc47000) /lib64/ld-linux-x86-64.so.2 (0x00007fa35e2f3000)Une référence manquante est ainsi facilement détectable car elle serait simplement marquée
not found
.
La commande nm permet d'afficher les symboles contenus dans les fichiers et objets.
# nm test 0000000000600bf8 B __bss_start U carre 0000000000600bf8 D _edata 0000000000600c00 B _end 0000000000400814 T _fini U foo w __gmon_start__ 00000000004005b8 T _init w _ITM_deregisterTMCloneTable w _ITM_registerTMCloneTable w _Jv_RegisterClasses U __libc_start_main U printf U putsLes types de symboles sont les suivants
Type de symbole | Description |
---|---|
A | La valeur du symbole est absolue et ne sera pas modifiée par une édition de liens ultérieure |
B | Section DATA non-encore initialisée |
D | Section DATA initialisée |
T | Section de code normale |
U | Symbole utilisé mais non-encore initialisé. Dépend d'une bibliothèque tierce |
W | Symbole doublement défini. La définition pourra être faite dans une autre bibliothèque pour résoudre les dépendances. |
La commande readelf permet de lister les symboles contenus dans un objet, notamment d'une bibliothèque partagée.
# readelf -s /usr/bin/gcc Table de symboles « .dynsym » contient 145 entrées : Num: Valeur Tail Type Lien Vis Ndx Nom 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __uflow@GLIBC_2.2.5 (2) 2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND mkstemps@GLIBC_2.11 (3) 3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getenv@GLIBC_2.2.5 (2) 4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND dl_iterate_phdr@GLIBC_2.2.5 (2) 5: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __pthread_key_create 6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND putchar@GLIBC_2.2.5 (2) 7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strcasecmp@GLIBC_2.2.5 (2) 8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND localtime@GLIBC_2.2.5 (2) 9: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strverscmp@GLIBC_2.2.5 (2) 10: 0000000000000000 0 FUNC GLOBAL DEFAULT UND abort@GLIBC_2.2.5 (2) 11: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __errno_location@GLIBC_2.2.5 (2) 12: 0000000000000000 0 FUNC GLOBAL DEFAULT UND unlink@GLIBC_2.2.5 (2)Consultez la page de manuel sur readelf pour plus d'informations.
L'autre façon d'utiliser une bibliothèque partagée est de la manipuler sous la forme d'un greffon (un plugin en bon jargon). Nous allons avoir une bibliothèque partagée qui sera utilisée plutôt depuis un seul programme pour lui apporter des fonctionnalités à la demande, plutôt qu'une bibliothèque partagée entre plusieurs programmes qui donnera accès à des fonctions utilisables par tous les programmes.
Prenons un nouvel exemple dans lequel un plugin contient deux fonctions mathématiques avancées à savoir Addition et Multiplication (niveau Math Sup svp). Commenons par le fichier Include plugin.h
#ifndef __PLUGIN__H__ #define __PLUGIN__H__ int Addition(int, int); int Multiplication(int, int); #endifPoursuivons par le fichier de description de code plugin.c
#include <stdio.h> int Addition(int a, int b) { return (a+b); } int Multiplication(int a, int b) { return (a * b); }Si nous compilons et produisons l'édition des liens de cette bibliothèque par
# gcc -Wall -Werror -fPIC -c plugin.c # gcc -shared -o plugin.so plugin.oNous obtenons un fichier ressemblant beaucoup à une bibliothèque partagée telle que définie dans les paragraphes précédents. Il y a un élément auquel il faut cependant prendre garde. Lorsque ces bibliothèques ont été créées, c'était pour les utiliser avec le Langage C. Si vous les utilisez avec du C++ par exemple, le compilateur C++ va avoir la fâcheuse tendance à modifier les noms des objets de bibliothèque pour les mettre à sa sauce, ce qui va casser les dépendances et rendre vos bibliothèques inutilisables. Pour résoudre ce problème, forcer la description "C" pour la génération des bibliothèques, même si vous utilisez un compilateur C++. Il faut pour cela modifier le fichier include comme ceci pour préciser au compilateur de gérer les fonctions externes en pur "C"
#ifndef __PLUGIN__H__ #define __PLUGIN__H__ #ifdef __cplusplus extern "C" { #endif int Addition(int, int); int Multiplication(int, int); #ifdef __cplusplus } #endif #endif
Une fois que le plugin est prêt à l'usage, il ne reste qu'à écrire le programme principal.
http://docs.python.org/library/ctypes.html : partie de la documentation Python relatant l’usage des conventions d’appels pour permettre l’utilisation de DLLs (Windows) et de fichiers .SO (Unix) avec Python.