Sommaire |
Imaginez : Vous avez développé une série de fonctions très utiles pour un projet, prenons l'exemple d'un tri par bulles. Un jour, vous devez effectuer une nouvelle série de tris. Vous ouvrez le projet et passez la journée à faire du copier coller ?!?! NON !!! car vous aviez pensé à créer un librairie contenant la fonction principale du tri, n'est ce pas ?
Les librairies permettent de se constituer des bibliothèques de fonctions réutilisables et distribuables.
Vous voulez un autre avantage des librairies ?
On les compile à part ! Si vous avez comme moi un pc qui se fait vieux, vous savez que la compilation peut prendre pas mal de temps. La bonne nouvelle c'est qu'une fois les librairies compilées, vous ne les recompilez pas à chaque fois que vous recompilez votre projet. Tout au plus, vous les liez, s'il s'agit de bibliothèques statiques.
Une librairie est composée d'un fichier d'en têtes (headers en anglais, d'ou le .h), ce sont les fichiers .h qui doivent vous dire quelque chose, non ? Et d'une partie binaire.
Le fichier d'en têtes, permet de "déclarer" les fonctions présentes dans la librairie, le type de valeurs qu'elles renvoient et les paramètres à leur passer.
La partie binaire est constituée d'un ou plusieurs fichiers c compilés. Ces fichiers c contiennent l'implémentation des prototypes de fonctions déclarées dans le fichier d'en têtes.
Vous pouvez modifier le code de la librarie autant que vous le désirez, du moment que vous respectez les prototypes définis dans le fichier .h.
Ouais, entrons dans le vif du sujet. Qu'est ce qu'une librairie statique ? et une librairie dynamique ? Quelles sont les avantages et inconvénients de chacune ?
Mais il y a aussi des inconvénients :
Nota Bene : les liens avec les librairies dynamiques ne se font pas toujours lors de l'exécution, un programme peut en effet charger lui même les librairies dont il a besoin quand il en a besoin (voir les fonctions dlopen() et dlsym()), Celà est utile pour construire l'application au fur et à mesure de son exécution. Imaginez par exemple que vous écriviez un programme de retouche d'image, pourquoi charger les libraries de traitement de fichier jpeg si pendant une "session" l'utilisateur ne se sert que de fichiers .png ?
Consultez les pages man de dlopen et dlsym et celles qui y sont conseillées et tapant dans la console
man dlopen man dlsym
Ces fonctions permettent notamment de réaliser des "plugins", c'est à dire des bout de codes qui sont chargés durant l'exécution, pour permettre par exemple d'étendre les fonctionalités d'un programme sans le modifier (à condition que ça été prévu). En pratique, un programme peut charger par dlopen beaucoup (des milliers au moins) de "plugins" ensemble.
Une autre possibilité est même de méta-programmer, c'est à dire d'écrire un programme qui génère le code pour le traitement approprié des données. Il suffit pour ça (par exemple) de générer à l'exécution du code C dans un fichier gen.c, de lancer depuis le programme sa compilation (avec la fonction system du C par exemple) en exécutant la commande gcc -O -fPIC -shared gen.c -o gen.so, puis de charger le code obtenu par dlopen
Si vous voulez que votre librairie soit réutilisable et efficace, vous devez respecter quelques notions fondamentales de la programmation.
| Pas Bien ! |
| Cette solution fait appel à une variable globale pour connaitre la taille du tableau. C'est déconseillé ! |
|
/* * ...
*/
int tailleTab;
long * traiteTab (long * tab) {
/*
*
* Traitement des données
*
*/
}
/*
* ...
*/
|
| Bien ! |
| Ici, la taille du tableau est passée en paramètre à la fonction. C'est mieux ! |
|
/* * ...
*/
long * traiteTab (long * tab, int tailleTab) {
/*
*
* Traitement des données
*
*/
}
/*
* ...
*/
|
| Si le fichier à été inclus, _MON_TEST_H est défini et il ne le sera pas à nouveau. |
|
#ifndef _MON_TEST_H #define _MON_TEST_H 1 /* * ... *ici, le contenu normal du fichier monTest.h * ... */ #endif |
La directive #include : pour inclure un fichier dans un autre en c, on utilise la directive #include.
On spécifie le chemin relatif entre le fichier incluant et le fichier inclus, par exemple :
#include "mesH/monTest.h"
ou bien on place le fichier .h dans le répertoire ou le compilateur s'attend à trouver les fichiers .h. (regardez dans /usr/include par exemple) et on donne le chemin relatif par rapport à ce répertoire, par exemple :
#include <orb/orbit.h>
Nota Bene : On peut également spécifier d'autres chemins de recherche grâce à l'option -I de gcc, par exemple, pour y inclure le répertoire courant :
gcc -I. etc...
Pour devenir incollables sur ces amusantes petites choses, apprenez par coeur les man pages de gcc et ld. ;-)
Consultez les pages man des fonctions d'allocation dynamique de mémoire, du compilateur, du linker et celles qui y sont conseillées et tapant dans la console :
man malloc man realloc man calloc man free man gcc man ld
On rentre enfin dans le vif du sujet avec cette partie. On va en effet maintenant suivre un exemple pas à pas pour construire une librarie, la compiler, l'utiliser dans un exécutable sous ses formes statiques et dynamiques.
On va prendre l'exemple d'un librairie comportant une fonction permettant de trier un tableau contenant des entiers longs. La méthode de tri sera le tri par bulles. Je ne suis pas vraiment sûr que ce soit cet algorithme là mais ça marche alors ... Roule !
Bien entrons dans le dedans du vif du sujet ! ouvrez un éditeur de texte et tapez ça dans un fichier tri_a_bulles.h :
tri_a_bulles.h /* * tri_a_bulles.h * * Quelques fonctions pour opérations basiques sur un tableau de longs. * * auteur: Xavier GARREAU : xgarreau@club-internet.fr * * web : http://perso.club-internet.fr/xgarreau/ * * dmodif: 13.03.2000 * */ #ifndef _TRI_A_BULLES_H #define _TRI_A_BULLES_H 1 /* * test_case_tableau : Dans le tableau pointé par addr_tableau, * Vérifie que la case case case_tableau est bien placée par rapport à celle qui la précède * Si ce n'est pas le cas, permute les cases et se rappelle sur la case précédente. * La récurrence s'arrête quand * la case case case_tableau est bien placée par rapport à celle qui la précède * ou bien si case_tableau vaut prem_case_tableau. * Ne renvoie rien. * */ void test_case_tableau ( long * addr_tableau, int taille_tableau, int case_tableau, int prem_case_tableau ); /* * permut_cases : * permute les valeurs des cases case_1 et case_2 du tableau pointé par addr_tableau * Ne renvoie rien. * */ void permut_cases ( long * addr_tableau, int case_1, int case_2 ); /* * test_tableau : * renvoie le tableau pointé par addr_tableau trié (ordre croissant) * de la case prem_case_tableau * à la case taille_tableau-1 * */ long * test_tableau ( long * addr_tableau, int prem_case_tableau, int taille_tableau ); /* * nb_cases_tableau : * Renvoie le nombre de cases du tableau pointé par addr_tableau * càd nombre de cases allouées * ou première case contenant (long)NULL si le tableau en contient une. * */ int nb_cases_tableau ( long * addr_tableau ); #endif
Si vous êtes familier avec les fichiers .h, pas de problème. Sinon, disons qu'on se contente de définir les "prototypes" des fonctions, on écrira leur corps dans un fichier .c.
Les fichiers .h permettent au compilateur de connaitre les prototypes des fonctions qu'il rencontre dans les différents fichiers.c qui les utilisent.
Je m'explique ! Les fonctions définies dans un fichier .c peuvent être utilisées dans un autre, ça vous le savez ! (hein ? vous le savez ?) Il suffit de préciser au compilateur tous les fichiers à compiler (gcc fic1.c fic2.c ... ficn.c). Si par hasard vous commettez une erreur en appelant une fonction, vous verrez que la compilation se passera sans problème, MAIS, lors de l'exécution vous obtiendrez des résultats inattendus ou pire, une erreur. Brrrrrrrrrrrr ... Flippant non ?
Bon, si on programmait ? Un petit peu de récursivité maintenant ? GO ! BANZAI !
Ouvrez un éditeur de texte et tapez ça dans un fichier tri_a_bulles.c :
tri_a_bulles.c /* * tri_a_bulles.c * * Quelques fonctions pour opérations basiques sur un tableau de longs. * * auteur: Xavier GARREAU : xgarreau@club-internet.fr * * web : http://perso.club-internet.fr/xgarreau/ * * dmodif: 13.03.2000 * */ #include "tri_a_bulles.h" void test_case_tableau ( long * addr_tableau, int taille_tableau, int case_tableau, int prem_case_tableau ) { if ( case_tableau > prem_case_tableau ) if (addr_tableau[case_tableau] < addr_tableau[case_tableau-1]) { permut_cases ( addr_tableau, case_tableau, case_tableau-1 ); test_case_tableau ( addr_tableau, taille_tableau, case_tableau-1, prem_case_tableau ); } } void permut_cases ( long * addr_tableau, int case_1, int case_2 ) { long tempo; tempo = addr_tableau[case_1]; addr_tableau[case_1] = addr_tableau[case_2]; addr_tableau[case_2] = tempo; } long * test_tableau ( long * addr_tableau, int prem_case_tableau, int taille_tableau ) { int i; for (i=prem_case_tableau ; i<taille_tableau ; i++) test_case_tableau (addr_tableau, taille_tableau, i, prem_case_tableau); return addr_tableau; } int nb_cases_tableau ( long * addr_tableau ) { int nb_cases; nb_cases=0; while (addr_tableau[nb_cases]) nb_cases++; return (nb_cases); }
Finalement ça s'est bien passé non ? Pas si compliqué !
Bon si on veut utiliser ça il va falloir créer une application qui en a besoin ! On y va ?
Maintenant que nous avons les sources de notre librairie, prêtes à être compilées et liées, il va falloir penser à construire une application pour utiliser les fonctions que l'on y a mis ! Ce sera bientôt chose faite si vous voulez bien vous prêter encore un peu au jeu de cette dernière fastidieuse saisie.
Ouvrez un éditeur de texte et tapez ça dans un fichier test.c :
test.c /* * test.c * * Une application qui utilise les fonctions de la librairie tri_a_bulles * * auteur: Xavier GARREAU : xgarreau@club-internet.fr * * web : http://perso.club-internet.fr/xgarreau/ * * dmodif: 14.03.2000 * */ #include <stdio.h> #include <stdlib.h> #include "tri_a_bulles.h" int main (int argc, char * argv[]) { long * tab; int i; /* créée un tableau de 10 longs */ tab = (long *)malloc( 10 * sizeof (long) ); /* Initialise le générateur de nombre aléatoires avec ... * L'heure de lancement ... * voir man random ou man rand pour le pourquoi de la chose !!! * voir man time pour le comment !!! */ srandom((int)time((time_t *)NULL)); /* remplit le tableau avec une suite de nombres pseudo-aléatoires * et affiche le contenu. */ for (i=0; i<10; i++) { tab[i] = random(); printf ("tableau[%d] = %ld\n", i, tab[i]); } /* Affiche le nombre de cases su tableau retourné par la librairie */ printf ("\nTaille du tableau : %d\n", nb_cases_tableau (tab)); /* Permute 2 cases et affiche le tableau résultant */ permut_cases (tab, 2, 8); printf ("\nTableau après permutation des cases 2 et 8.\n"); for (i=0; i<10; i++) printf ("tableau[%d] = %ld\n", i, tab[i]); /* Trie le tableau et affiche le résultat */ printf ("\nTableau trié par la fonction de la librairie\n"); test_tableau ( tab, 0, nb_cases_tableau (tab) ); for (i=0; i<10; i++) printf ("tableau[%d] = %ld\n", i, tab[i]); return 0; }
Bien ! Maintenant qu'on a tous les bouts, on va pouvoir compiler, lier, exécuter, etc...
Bon, et bien, nous y voilà, on a tout ! Il ne nous reste plus qu'à tout mettre nesemble selon différentes méthodes. On va commencer par la méthode du projet unique, sans librairies.
Après ça, on va se faire une petite librairie statique, ensuite, on va mettre en place une librarie dynamique (ou shared object, .so chez les pingouins, .dll chez les défenestrés !)
On n'abordera toutefois pas le cas de la construction des dll car franchement, ce serait perdre du temps pour rien. Je suis pour laisser les gens qui n'ont que ça pour occupper leurs tristes journées générer et utiliser (de façon HYPER galère) les librairies dynamiques en envirronnement ouinouin. Ici, je pense que nous sommes entre gens sérieux, on développe donc sous linux, FreeBSD, solaris ou autres unix. Franchement, je m'excuse de sembler aussi méchant vis à vis de wintruc mais quand vous aurez comparé les qualités des deux systèmes (au moins en matière de support de développement), je suis presque sûr que vous penserez comme moi.
A la fin de cette série d'infos, je vous renverrai sur les bons coins pour utiliser les makefiles, ainsi que les merveilleux outils GNU que sont autoconf, autoscan, automake et leurs copains.
| J'allais oublier !!! |
|
Normalement, le programme affiche un truc comme ça : xavier@Rooty Rep: tabLong $ ./test tableau[0] = 1613489603 tableau[1] = 866884903 tableau[2] = 295298324 tableau[3] = 1614953842 tableau[4] = 1111079167 tableau[5] = 950260573 tableau[6] = 901366332 tableau[7] = 745370511 tableau[8] = 2063084132 tableau[9] = 374329280 Taille du tableau : 10 Tableau après permutation des cases 2 et 8. tableau[0] = 1613489603 tableau[1] = 866884903 tableau[2] = 2063084132 tableau[3] = 1614953842 tableau[4] = 1111079167 tableau[5] = 950260573 tableau[6] = 901366332 tableau[7] = 745370511 tableau[8] = 295298324 tableau[9] = 374329280 Tableau trié par la fonction de la librairie tableau[0] = 295298324 tableau[1] = 374329280 tableau[2] = 745370511 tableau[3] = 866884903 tableau[4] = 901366332 tableau[5] = 950260573 tableau[6] = 1111079167 tableau[7] = 1613489603 tableau[8] = 1614953842 tableau[9] = 2063084132 En scoop, vous apprenez que mon pc s'appelle Rooty (ce qui vient des Régulateurs de Bachman), que je me connecte sous le nom de xavier et que ce projet se trouve dans le répertoire tabLong. En outre le symbole $ précise que je ne suis qu'un simple utilisateur, le root ayant droit à un superbe #. Voilà ! |
Pour ce qui est du renvoi sur la doc sur autoscan, autoconf, automake et leurs copains ce que j'ai trouvé de plus sympa, ce sont les infos pages du gnome-help-browser. Si vous ne l'avez pas installé, vous pouvez les consulter dans la console en tapant info automake ou info autoscan, etc ... !
Pour trouver moults docs de developpement, voyez http://developer.gnome.org/. Il existe la même chose avec les libs kde sur http://developer.kde.org/. Il existe bien d'autres sources d'infos mais ce sont celles que je préfère.
Un petit conseil, si vous aimez le c, installez les librairies gtk+/gdk/glib/imlib/ORBit C'est le top.
Vous cherchez un environnement de développement ? Choisissez gIDE et glade si vous avez des affinités avec le projet et les librairies du projet GNOME ou kdevelop pour affinités avec KDE.
Comme débogueur je dois dire que kdbg est génial, d'autant qu'il s'intègre via DCOP dans kdevelop. Mais xemacs est pas mal non plus et gdb tout seul aussi, le tout c'est de connaitre !
En bref, prenons ce qui existe de meilleur partout. Bienvenue dans linux et à bientôt pour de nouvelles aventures ...
Copyright © 03/01/2001, Xavier GARREAU (alaide)
| | Ce document est publié sous licence Creative Commons Attribution, Partage à l'identique, Contexte non commercial 2.0 : http://creativecommons.org/licenses/by-nc-sa/2.0/fr/ |