Retour

Créer et utiliser une librairie en C

Qu'est-ce qu'une librairie

La notion de librairie

Avez-vous consulter la page consacrée à la résolution d'un système linéaire, plus précisément les programmes PivotGauss et DecompositionLU ? Vous avez alors sans doute remarqué que les deux programmes utilisent les mêmes routines de gestion de la mémoire. En programmation, c'est un constat très fréquent: les programmes d'une même application ou d'un même domaine d'applications utilisent très fréquemment les mêmes routines: calcul vectoriel, calcul complexe, gestion de fichiers, gestion de tables, graphique, etc..
Le premier réflexe est de recopier les sources de ces routines dans chacun des sources des programmes utilisateurs. C'est ce que j'ai fait, pour l'exemple, dans les programmes mentionnés plus haut. Mais ce n'est pas terrible: cela augmente la taille des fichiers sources, parfois énormément, et surtout, si vous faites une modification dans une routine, il faut la reporter dans tous les sources, sans en oublier un, ce qui n'est pas gagné !
Il faut donc trouver une solution plus efficace, comme par exemple rassembler toutes les routines d'un même usage dans un fichier unique. On constitue en quelque sorte une bibliothèque de toutes les routines utilisées pour gérer la mémoire, ou faire du calcul matriciel ou encore du graphisme 3D. Ce sont ces fichiers, qui contiennent le code objet des routines, que l'on nomme "library" en anglais, et "librairie" par habitude, mais c'est un mauvais angliscisme, et plus justement "bibliothèque" en français.
En effet, l'usage des libraries (oui je sais c'est impropre, mais c'est l'habitude...) est aussi vieux que la programmation. D'ailleurs, tous les systèmes d'exploitation en contiennent une foultitude! N'importe quelle application est formée de quelques programmes et d'une ribambelle de librairies que vous pouvez identifier sur votre disque (sous Windows, comptez donc le nombre de dll, qui sont les libraries dynamiques de Windows..).
Nous allons donc, sans entrer dans des détails trop informaticiens, étudier pourquoi et comment construire une librairie, en s'appuyant sur l'exemple des programmes PivotGauss et DecompositionLU.

Pourquoi utiliser une librairie

Les principaux avantages de l'usage de librairies sont:

Pour résumer, vous construirez une librairie quand vous disposerez d'un ensemble de routines, parfaitement testées, qui sont susceptibles d'être utilisées dans plusieurs programmes et qui ont une utilité fonctionnelle commune. Un conseil cependant, ne faites pas des librairies trop grosses!

Les deux principaux types de librairie

Les librairies statiques

On les appelle aussi librairie (ou bibliothèques) objet. Une librarie statique est un fichier qui contient le code objet (celui issu de la compilation) des routines constituant la librairie. Lors de l'opération de link, les librairies statiques seront liées au code objet du programme principal (le "main" en C) pour former le code exécutable du programme (le ".exe" sous Windows).
Les librairies statiques n'ont qu'un seul avantage: la simplicité d'écriture, comme vous pouvez en juger en lisant le code de LibMatrice.c ! Il suffit de créer un fichier source (.c) contenant le code des routines et c'est tout ! On verra dans le chapitre suivant comment faire pratiquement.
Par contre elles souffrent de plusieurs inconvénients, surtout si leur taille est importante. Le moindre est qu'elles augmentent la taille du programme exécutable auquel elles sont liées. Mais le pire n'est pas là... Lorsque le système d'exploitation (Windows ou Linux) charge un programme pour l'exécuter, il charge également en mémoire une copie de chaque librairie statique (puisqu'elles sont incluses dans le code exécutable du programme). Imaginez donc que l'on fasse tourner 10 ou 20 programmes qui utilisent chacun plusieurs librairies statiques identiques de quelques mégaoctets. Voilà votre mémoire vive de PC occupée par plusieurs dizaines de mégaoctets qui pourraient être économisés, parce qu'il s'agit de copies multiples d'un même objet. Et ça, c'est très génant, parce que certaines librairies sont très grosses et très utilisées !
En résumé, sauf pour les petites librairies utilisées par peu de programmes, n'utilisez pas les librairies statiques, mais plutôt les librairies dynamiques.

Les librairies dynamiques

Je ne parlerai ici que des librairies dynamiques sous Windows, les célèbres dll (dynamic link libraries). Comme l'indique le nom, une dll est chargée dynamiquement en mémoire, c'est à dire quand on en a besoin. Lorsque un programme a besoin d'une routine contenue dans une dll, il vérifie que cette dll n'est pas chargée en mémoire. Si c'est le cas, il y accède car les routines d'une dll sont ré-entrantes, c'est à dire qu'elles peuvent être utilisées par plusieurs programmes en même temps. Si ce n'est pas le cas, le programme la charge en mémoire. Elle sera ainsi disponible pour tous les programmes qui voudraient y accéder.
Vous voyez d'ici l'avantage des dll par rapport aux librairies statiques : un moindre encombrement de la mémoire. Mais au prix d'une complexité d'écriture plus grande que pour une librairie statique. Examinez donc le code source de DllMatricesMain, qui fait exactement la même chose que LibMatrices. Bon, il est vrai que l'EDI en fait un peu pour vous lorsqu'il s'agit de construire une dll, on le verra ci-dessous.
Les dll ont aussi quelques inconvénients. Tout d'abord d'ordre pratique : pour lancer un programme, il faut disposer de toutes les dll qui lui sont nécessaires. Qui n'a pas pesté (au moins) contre une dll manquante ou de version incompatible! Enfin, l'usage d'une dll est inutile dans le cas de l'appel des routines par un seul programme: cela n'économise pas de place et alourdi le développement.
En résumé, lorsqu'une librairie est utilisée par de nombreux programmes, il est utile de créer et utiliser une dll.

Construire une librairie sous Dev-ccpp

J'ai choisi de travailler sur les exemples ci-dessous à l'aide de l'EDI Dev-cpp, que j'ai déjà abordé dans la page d'initiation au C. Cet EDI utilise le compilateur C/C++ de GNU, le gcc. Les méthodes décrites ci-dessous sont quasiment transposables en l'état à l'EDI Microsoft Visual Sudio. Pour ceux qui n'hésiteront pas à faire eux-même leurs makefiles (et ils comprendront ce que je veux dire), ils peuvent travailler sous Eclipse ou autre EDI.
Pour illustrer les méthodes, j'ai choisi de construire une librairie contenant les routines de gestion de mémoire pour les vecteurs et les matrices et d'affichage de ces objets, routines qui sont utilisées dans les programmes PivotGauss et DecompositionLU, et qu'on utilisera dans d'autres programmes C.

Cas d'une librairie statique

Comment la construire

Une librairie statique est très facile à construire, surtout sous Dev-cpp !
Lancez le programme Devcpp. Lorsque la fenêtre principale sera ouverte, allez dans le menu principal et choisissez Fichier/Nouveau/Projet. Une fenêtre apparaît, qui vous propose différentes options pour votre nouveau projet: sélectionnez "Static Library". Dans la partie "Option du projet" de la fenêtre, donnez le nom de la librairie, soit "LibMatrices" et sélectionnez le bouton radio "Projet C", puis cliquez sur OK.
Une fenêtre s'ouvre pour vous proposer de sauvegarder le fichier projet en le nommant. Sélectionnez le répertoire de sauvegarde qui vous convient, et saisissez le nom du projet, ici "LibMatrices". Appuyez sur le bouton Enregistrer.

Créons le fichier source de la librairie. Dans le menu principal de Dev-cpp, allez dans Projet puis cliquez sur "Nouveau fichier source". L'éditeur s'active et vous permet de saisir le code de votre librairie. Dans notre cas, vous recopierez le code des routines à partir du source de DecompositionLU.c (de la routine *vecteur jusqu'à la routine zeros), sans oublier les includes de stdlib, stdio et malloc, dans la zone d'édition de Dev-ccp.
Une fois cette opération de recopie effectuée, il ne vous reste plus qu'à enregistrer votre fichier. Pour ce faire, allez dans Fichier/Sauvegarder Sous.. Sélectionnez le répertoire de sauvegarde qui vous convient, et saisissez le nom du code source, ici "LibMatrices". Appuyez sur le bouton Enregistrer.

Pour créer votre librairie, il faut maintenant compiler le code source que vous venez de saisir (ou de copier...). Dans le menu principal, allez dans Exécuter/Compiler. Et en principe, si vous avez suivi mes indications, le compilateur construit le fichier librairie LibMatrices.a. Vous voilà heureux possesseur d'une librairie statique!

Le code résultant

Le code source de LibMatrices.c montre sa simplicité. En cas de nécessité, vous pouvez même télécharger le fichier projet LibMatrices.dev, mais il ne présente aucune particularité...

Cas d'une librairie dynamique

Comment la construire

Une librairie dynamique est un peu plus délicate à construire, vraiment un tout petit peu plus !
Lancez le programme Devcpp. Lorsque la fenêtre principale sera ouverte, allez dans le menu principal et choisissez Fichier/Nouveau/Projet. Une fenêtre apparaît, qui vous propose différentes options pour votre nouveau projet: sélectionnez "DLL". Dans la partie "Option du projet" de la fenêtre, donnez le nom de la librairie, soit "DllMatrices" et sélectionnez le bouton radio "Projet C", puis cliquez sur OK.
Une fenêtre s'ouvre pour vous proposer de sauvegarder le fichier projet en le nommant. Sélectionnez le répertoire de sauvegarde qui vous convient, et saisissez le nom du projet, ici "DllMatrices". Appuyez sur le bouton Enregistrer.

Vous constatez l'ouverture d'un fichier dans la zone d'édition, qui vous invite à compléter le fichier header de la dll. Il contient une ligne exemple (DLLIMPORT void HelloWorld (void);) que vous pouvez effacer, après l'avoir notée comme exemple. En effet, pour chacune des routines qui seront importées de la dll, il convient de créér les mêmes lignes. pour notre cas cela donne:

DLLIMPORT double *vecteur(int n);

DLLIMPORT double **matrice(int n);

DLLIMPORT void libere_vecteur(double *v);

DLLIMPORT void libere_matrice(double **m, int n);

DLLIMPORT void affiche_vecteur(double *v, int n);

DLLIMPORT void affiche_matrice(double **m, int n);

DLLIMPORT void zeros(double **m, int n);

Sauvegardez le fichier header: allez dans Fichier/Sauvegarder Sous.. Sélectionnez le répertoire de sauvegarde qui vous convient, et saisissez le nom du fichier header, ici "DllMatrices". Faites attention à sélectionner l'extension ".h" et non ".cpp". Appuyez sur le bouton Enregistrer.

On va maintenant compléter le fichier source de la dll, qui s'appelle provisoirement dllmain.c. Dans celui-ci:

Une fois cette opération de recopie effectuée, il ne vous reste plus qu'à enregistrer votre fichier. Pour ce faire, allez dans Fichier/Sauvegarder Sous.. Sélectionnez le répertoire de sauvegarde qui vous convient, et saisissez le nom du code source, ici "DllMatricesMain". Appuyez sur le bouton Enregistrer.

Pour créer votre librairie, il faut maintenant compiler et linker le code source que vous venez de saisir (ou de copier...). Dans le menu principal, allez dans Exécuter/Compiler&Exécuter. Et en principe, si vous avez suivi mes indications, le compilateur construit le fichier librairie DllMatrices.dll. Vous voilà heureux possesseur d'une librairie dynamique !

Le code résultant

Le source est décomposé en deux fichiers:DllMatricesMain.c et DllMatrices.h. Vous pouvez également télécharger le fichier projet DllMatrices.dev.

Utiliser une librairie dans un programme

Cas d'une librairie statique

Afin d'illustrer l'emploi d'une librairie statique, j'ai réécris le code source du programme DecompositionLU.c que j'ai appelé DecompositionLULib.c. Téléchargez son code source.
Remarquez qu'il ne présente pas de particularité si ce n'est de ne plus contenir les sources des routines *vecteur et les autres ! Si vous le chargez dans Dev-cpp et que vous tentiez de le compiler et de le linker en l'état, vous allez avoir quelques soucis: il ne trouve pas les routines *vecteur et ses petites soeurs... En effet, il faut indiquer au linker l'endroit où il peut trouver ces routines.
Cela se fait très simplement : dans le menu principal de Dev-cpp, allez dans Projet/Options du projet. Une fenêtre s'ouvre, munie de plusieurs onglets. Allez dans l'onglet Paramètres. Dans la colonne de droite "Editeur de liens", ajoutez "LibMatrices.a", sans les guillemets bien sur.
Je suppose que le fichier LibMatrice.a est dans le même répertoire que votre fichier projet. Dans le cas contraire, toujours dans la fenêtre "Options du projet", allez dans l'onglet Répertoire et indiquez dans "Répertoire bibliothèques", le chemin du répertoire qui contient vos librairies.
Vous pouvez maintenant recompiler et relinker votre programme DecompositionLULib et tout ira bien. Le linker ira chercher les routines manquantes dans la librairie qu'on lui aura indiqué, soit LibMatrices.a.

Pour vous aidez, vous pouvez télécharger le fichier projet DecompositionLULib.dev.

Cas d'une librairie dynamique

Comme pour la librairie statique, j'ai réécris le code source du programme DecompositionLU.c que j'ai appelé DecompositionLUDll.c, en utilisant la dll DllMatrices.dll. Téléchargez son code source.
Remarquez qu'il ne présente pas de particularité si ce n'est de ne plus contenir les sources des routines *vecteur et les autres et aussi de comprendre une ligne d'include supplémentaire ! En effet, il faut indiquer au programme quelles sont les routines importées depuis la dll. Ces informations sont dans le fichier header DllMatrices.h, qu'il faut donc inclure.
Comme dans le cas de la librairie statique, il faut indiquer au linker le nom de la dll. Cela se fait de la même façon : dans le menu principal de Dev-cpp, allez dans Projet/Options du projet. Une fenêtre s'ouvre, munie de plusieurs onglets. Allez dans l'onglet Paramètres. Dans la colonne de droite "Editeur de liens", ajoutez "DllMatrices.dll", sans les guillemets.
Je suppose que le fichier DllMatrices.dll est dans le même répertoire que votre fichier projet. Dans le cas contraire, toujours dans la fenêtre "Options du projet", allez dans l'onglet Répertoire et indiquez dans "Répertoire bibliothèques", le chemin du répertoire qui contient vos librairies.
Vous pouvez maintenant compiler et linker votre programme DecompositionLUDll.

Pour vous aidez, vous pouvez télécharger le fichier projet DecompositionLUDll.dev.

Pour conclure

Vous pouvez maintenant étendre votre usage des librairies à toutes les routines que vous voudrez partager entre plusieurs programmes. C'est aussi une introduction à l'usage de librairies extérieures de calcul ou de graphiques. Vous avez déjà noté que j'utilisais fréquement dislin ! Je compte aussi utiliser plus communément les librairies GSL, SLATEC et même pourquoi pas Root ! Cette dernière est un sacré morceau, en provenance directe du CERN !


Contenu et design par Dominique Lefebvre - www.tangenteX.com mars 2013 -- Vous pouvez me joindre par mail ou sur PhysiqueX

Licence Creative Commons

Cette œuvre est mise à disposition selon les termes de la Licence Creative Commons Attribution - Pas d’Utilisation Commerciale - Pas de Modification 3.0 France .