Les DLLs sous Unix

Introduction

Le document suivant montre comment créer et utiliser des Dlls sous Linux, il est probable qu'il faille adapter les scripts de compilation sur d'autres versions d'Unix.

L'utilisation des DLLs sous Unix est très semblable à ce qui se fait sous Windows. Sous Unix, l'extension des DLLs est en général .so, et il est courant que le numéro de version soit ajouté derrière l'extension (.so.1, .so.2), un lien symbolique pointant sur la version à utiliser.

Il est possible de lier l'application statiquement ou dynamiquement à la DLL.

DLL hell

Cette expression n'est pas inconnue des développeurs et des utilisateurs de Windows. Qui n'a pas eu un jour des conflits de versions entre applications Windows suite à une mise à jour ?

Tout comme pour Windows, il faut faire très attention en terme d'interface lorsqu'on décide de la création d'une DLL : Si les prototypes des fonctions ou les structures que l'on passe en argument changent entre 2 versions, cela risque de compromettre le fonctionnement des anciennes applications si cela n'a pas été prévu par le programmeur.

Une solution simple empruntée à Windows peut être de toujours mettre la taille d'une structure en tant que premier membre, et de changer le nom du prototype des fonctions si l'interface change :

// .h (interface de la dll)
// Structure version 1
typedef struct MaStructV1
{
    int cb;	// Doit être initialisée par l'application
    int a;  // Quelques membres ...
    int b;	//
} MaStructV1;

// Structure version 2
typedef struct MaStructV2
{
    int cb;	// Idem
    int a;
    int b;  // Si l'un des mebre devient obsolète, il faut quand même le conserver
    int c;	// Nouveau mebre
} MaStructV2;

// Fonction version 1
void maFonction( int a );

// Si le proto change, on change de nom
void maFonction2( int a, int b );

// Prototype avec pointeur
void monAutreFonction( MaStructV2 * pMaStructV2 )

// .c (code de la dll)
void monAutreFonction( MaStructV2 * pMaStructV2 )
{
    // On determine la version
    if( pMaStructV2->cb == sizeof(MaStructV1) )
    {
        // C'est une vieille appli V1 qui nous appelle
        ...
    }
    else if( pMaStructV2->cb == sizeof(MaStructV2) )
    {
        // C'est une appli V2 qui nous appelle
        ...
    }
}


Utilisation statique

Au niveau du code, il n'y a rien de spécial à faire, c'est dans le makefile que l'on indique que le programme a besoin de la DLL.

Pour que le programme fonctionne, il faut mettre à jour la variable d'environnement LD_LIBRARY_PATH qui contient le chemin des DLLs, et ce, même si la DLL est dans le répertoire courant.


# compilation du programme : La seule difference par rapport
# à une compilation classique est que l'on indique le .so
# (libshared.so) au lieu du .o
hellostat: hellostat.o libshared.so
    gcc hellostat.o libshared.so -o hellostat

# compilation de la dll
libshared.so: shared.o
    gcc -shared -o libshared.so shared.o

# on remarque l'option -fpic
shared.o: shared.cpp
    gcc -c -fpic shared.cpp -o shared.o


Utilisation dynamique

On utilise l' API dlopen() pour ouvrir la DLL et dlsym() pour aller chercher le pointeur de fonction.

Cette manière de faire est similaire à Windows (utilisation de LoadLibrary() et GetProcAddress()).

// Traitement des erreurs omis pour + de clarete
typedef void (*PFHelloShared)(int );

void *module;
PFHelloShared pfHelloShared;
// ouvre la DLL
module = dlopen("./libshared.so", RTLD_LAZY);

// Recupere un pointeur sur la fonction
pfHelloShared = (PFHelloShared)dlsym(module, "HelloShared");

// Lance la fonction
(*pfHelloShared)(8888);

Dans ce cas, pas besoin de mettre à jour LD_LIBRARY_PATH.

Sources

Valid XHTML 1.0!