5. Greffons en forme de bibliothèques partagées

Dans le précédent article, je vous avais parlé de dlopen(). Je vous ai tenu en haleine jusqu'à cet article, et pendant quelques paragraphes encore. Mais chose promise, chose due. Voici le fonctionnement de dlopen(). Deux choses encore: un, l'article précédent vous sera nécessaire si vous ne savez pas créer de bibliothèque dynamique, et deux, utiliser dlopen() et ses amis est une chose simple.

5.1. Rappel sur la création d'une bibliothèque partagée

Dans l'article précédent, j'avais utilisé le code suivant qui nous servira à nouveau, accessoirement en tant que bibliothèque partagée, et principalement en tant que greffon.

 1 #include <fstest.h>
 2 #include <sys/vfs.h>
 3 
 4 int
 5 fs_test (char *path, fstest_t * f)
 6 {
 7   struct statfs buf;
 8   if (!path)
 9     return (-1);
10   if (statfs (path, &buf) >= 0)
11     {
12       f->free = (buf.f_bsize / 1024) * buf.f_bavail / 1024;
13       f->total = (buf.f_bsize / 1024) * buf.f_blocks / 1024;
14       f->percent_free = 100 - (100 * buf.f_bavail) / buf.f_blocks;
15     }
16   else
17     {
18       return (-1);
19     }
20   return (0);
21 }

Appelez ce fichier fstest.c et compilez-le avec:

$ gcc -fPIC -c -Wall -I. fstest.c
$ gcc -shared -Wl,-soname,libfstest.so.1 -Wall fstest.o -o libfstest.so.1.0

Ceci est tout ce que je rappellerai de l'article précédent puisque seul ceci est nécessaire, le reste étant accessoire pour cet article.

5.2. Différence entre une bibliothèque partagée et un greffon

Si vous n'avez pas remarqué, votre compilateur vous l'a reproché: il manque le fichier fstest.h>. Je ne reprendrai pas le même que celui de l'article précédent. En effet, si la structure fstest_t est inchangée, la déclaration du prototype de la fonction fs_test() change. La différence entre une bibliothèque partagée généraliste et un greffon réside dans le fait que vous écrivez le prototyle des fonctions que vous appelez dans le fichier d'en-tête pour une bibliothèque partagée et que vous l'écrivez dans le programme chargeur dans le cas du greffon. Cependant, pour ne pas risquer d'avoir une API différente entre le chargeur et le greffon, nous allons quand même mettre le prototype de la fonction d'une manière différente dans le fichier d'en-tête de la bibliothèque partagée.

5.3. Pointeurs sur fonctions

Dans le programme principal, comme nous ne connaissons rien du greffon, nous allons devoir récupérer l'adresse des fonctions et mettre ces adresses dans des pointeurs. Ce sont les pointeurs sur fonction. Comment les déclare-t-on? Cette déclaration est un savant mélange entre un pointeur classique et un prototype de fonction. En fait, c'est facile: il suffit d'écrire le prototype de la fonction, avec bien sur le point-virgule à la fin. Puis vous mettez entre parenthèses le nom de la fonction, et vous le faites précéder par une étoile qui est aussi à l'intérieur des parenthèses. Vous pouvez changer le nom si vous le voulez puisque ce n'est alors plus le nom de la fonction mais le nom du pointeur sur la fonction qui se trouve entre parenthèses et précédé de l'étoile. Exemple : Voici une fonction:

int ajouter_deux_entiers (int a, int b);

Pour déclarer un pointeur sur cette fonction, pointeur que l'on appellera p_ajouter_deux_entiers, voici la ligne correspondante:

int (*p_ajouter_deux_entiers) (int a, int b);

Comme nous allons utiliser un pointeur sur fonction dans le chargeur, il nous faut recopier le prototype défini dans le fichier d'en-tête de la bibliothèque partagée pour le transformer comme nous l'avons vu. Cela est indésirable puisque cela entraîne de la redondance de code, ce qui est toujours mauvais. Que se passe-t-il si l'API du greffon change? Lors de la compilation du chargeur, le préprocesseur n'y verra rien et le greffon sera incompatible avec le chargeur sans que personne n'ait été averti.

Il nous faut utiliser typedef pour la fonction tout comme nous l'utilisons déjà pour la structure. Si vous venez de faire votre baptème du pointeur sur fonction dans les lignes précédentes, vous n'allez pas forcément avoir l'idée de la définition du typedef immédiatement. Pourtant elle est facile: il suffit de prendre la définition du pointeur sur fonction que nous avons vu avant, et de faire précéder l'ensemble par typedef. Définissons le type f_ajouter_deux_entiers à l'aide d'un typedef:

typedef int (*f_ajouter_deux_entiers) (int a, int b);

Et dans la suite du code, au lieu de mettre ceci

int (*p_ajouter_deux_entiers) (int a, int b);

vous mettre cela:

f_ajouter_deux_entiers p_ajouter_deux_entiers;

L'appel à p_ajouter_deux_entiers() se fera ensuite naturellement comme si la fonction faisait partie du chargeur. Il reste juste à faire pointer le pointeur sur fonction sur la fonction, ce que nous allons voir dans quelques paragraphes.

5.4. Le fichier d'en-tête de notre greffon

 1 #ifndef __LIBFSTEST_H__
 2 #define __LIBFSTEST_H__
 3 
 4 typedef struct
 5 {
 6   int percent_free;
 7   long free;
 8   long total;
 9 }
10 fstest_t;
11 
12 typedef int (*fs_test_f) (char *path, fstest_t * f);
13 
14 #endif

Par rapport à l'article précédent, la seule différence est la ligne 12 que j'ai transformée pour créer le type fs_test_f. Contrairement à l'article précédent où fs_test() était déclarée dans ce fichier d'en-tête inclus dans le programme principal, la fonction n'est déclarée nulle part à partir de maintenant. Comme je l'ai dit, nous la déclareront comme n'importe quelle autre variable, comme n'importe quel pointeur, avec ceci:

fs_test_f fs_test;

Vous pouvez donc maintenant vraiment compiler la bibliothèque partagée en appelant ce fichier fstest.h;

création est mise à disposition sous un contrat Creative Commons