Lier un exécutable à une DLL

Un fichier exécutable est lié à (ou charge) une DLL de l’une des deux manières suivantes :

  • Liaison implicite, où le système d’exploitation charge la DLL en même temps que l’exécutable qui l’utilise. L’exécutable client appelle les fonctions exportées de la DLL de la même façon que si les fonctions étaient liées statiquement et contenues dans l’exécutable. La liaison implicite est parfois appelée liaison dynamique de charge statique ou de temps de chargement.

  • Liaison explicite, où le système d’exploitation charge la DLL à la demande au moment de l’exécution. Un exécutable qui utilise une DLL en liant explicitement doit charger et décharger explicitement la DLL. Il doit également configurer un pointeur de fonction pour accéder à chaque fonction qu’elle utilise à partir de la DLL. Contrairement aux appels aux fonctions d’une bibliothèque liée statiquement ou d’une DLL implicitement liée, l’exécutable client doit appeler les fonctions exportées dans une DLL liée explicitement via des pointeurs de fonction. La liaison explicite est parfois appelée liaison dynamique ou liaison dynamique au moment de l’exécution.

Un exécutable peut utiliser l’une ou l’autre méthode de liaison pour établir un lien vers la même DLL. De plus, ces méthodes ne s’excluent pas mutuellement ; un exécutable peut lier implicitement à une DLL, et un autre peut l’attacher explicitement.

Déterminer la méthode de liaison à utiliser

L’utilisation d’une liaison implicite ou d’une liaison explicite est une décision architecturale que vous devez prendre pour votre application. Il existe des avantages et des inconvénients pour chaque méthode.

Liaison implicite

La liaison implicite se produit quand le code d’une application appelle une fonction DLL exportée. Lorsque le code source de l’exécutable appelant est compilé ou assemblé, l’appel de fonction DLL génère une référence de fonction externe dans le code objet. Pour résoudre cette référence externe, l’application doit établir un lien avec la bibliothèque d’importation (fichier.lib) fournie par le créateur de la DLL.

La bibliothèque d’importation contient uniquement du code pour charger la DLL et implémenter des appels à des fonctions dans la DLL. La recherche d’une fonction externe dans une bibliothèque d’importation informe l’éditeur de liens que le code de cette fonction se trouve dans une DLL. Pour résoudre les références externes aux DLL, l’éditeur de liens ajoute simplement des informations au fichier exécutable qui indique au système où trouver le code DLL au démarrage du processus.

Lorsque le système démarre un programme qui contient des références liées dynamiquement, il utilise les informations contenues dans le fichier exécutable du programme pour localiser les DLL requises. S’il ne peut pas localiser la DLL, le système met fin au processus et affiche une boîte de dialogue qui signale l’erreur. Sinon, le système mappe les modules DLL dans l’espace d’adressage du processus.

Si l’une des DLL a une fonction de point d’entrée pour l’initialisation et le code d’arrêt, par DllMainexemple, le système d’exploitation appelle la fonction. L’un des paramètres passés à la fonction de point d’entrée spécifie un code qui indique que la DLL est attachée au processus. Si la fonction de point d’entrée ne retourne pas TRUE, le système met fin au processus et signale l’erreur.

Enfin, le système modifie le code exécutable du processus pour fournir des adresses de départ pour les fonctions DLL.

Comme le reste du code d’un programme, le chargeur mappe le code DLL dans l’espace d’adressage du processus au démarrage du processus. Le système d’exploitation le charge en mémoire uniquement si nécessaire. Par conséquent, les PRELOAD attributs de code utilisés LOADONCALL par les fichiers .def pour contrôler le chargement dans les versions précédentes de Windows n’ont plus de signification.

Liaison explicite

La plupart des applications utilisent la liaison implicite, car il s’agit de la méthode de liaison la plus simple à utiliser. Toutefois, il existe des moments où une liaison explicite est nécessaire. Voici quelques raisons courantes d’utiliser la liaison explicite :

  • L’application ne connaît pas le nom d’une DLL qu’elle charge jusqu’au moment de l’exécution. Par exemple, l’application peut obtenir le nom de la DLL et les fonctions exportées à partir d’un fichier de configuration au démarrage.

  • Un processus qui utilise la liaison implicite est arrêté par le système d’exploitation si la DLL n’est pas trouvée au démarrage du processus. Un processus qui utilise la liaison explicite n’est pas arrêté dans ce cas et peut tenter de récupérer à partir de l’erreur. Par exemple, le processus peut avertir l’utilisateur de l’erreur et demander à l’utilisateur de spécifier un autre chemin d’accès à la DLL.

  • Un processus qui utilise la liaison implicite est également arrêté si l’une des DLL qu’elle est liée pour avoir une DllMain fonction qui échoue. Un processus qui utilise la liaison explicite n’est pas terminé dans cette situation.

  • Une application qui lie implicitement à de nombreuses DLL peut être lente à démarrer, car Windows charge toutes les DLL lorsque l’application se charge. Pour améliorer les performances de démarrage, une application peut uniquement utiliser la liaison implicite pour les DLL requises immédiatement après le chargement. Il peut utiliser une liaison explicite pour charger d’autres DLL uniquement quand elles sont nécessaires.

  • La liaison explicite élimine la nécessité de lier l’application à l’aide d’une bibliothèque d’importation. Si les modifications apportées à la DLL entraînent la modification des ordinals d’exportation, les applications n’ont pas besoin de relinker s’ils appellent GetProcAddress à l’aide du nom d’une fonction et non d’une valeur ordinale. Les applications qui utilisent la liaison implicite doivent toujours relinker à la bibliothèque d’importation modifiée.

Voici deux dangers de liaison explicite à connaître :

  • Si la DLL a une fonction de point d’entrée DllMain , le système d’exploitation appelle la fonction dans le contexte du thread appelé LoadLibrary. La fonction de point d’entrée n’est pas appelée si la DLL est déjà attachée au processus en raison d’un appel précédent à LoadLibrary celui-ci n’a pas eu d’appel correspondant à la FreeLibrary fonction. La liaison explicite peut entraîner des problèmes si la DLL utilise une DllMain fonction pour initialiser chaque thread d’un processus, car les threads qui existent déjà quand LoadLibrary (ou AfxLoadLibrary) sont appelés ne sont pas initialisés.

  • Si une DLL déclare des données d’étendue statique en tant que __declspec(thread), elle peut provoquer une erreur de protection si elle est explicitement liée. Une fois la DLL chargée par un appel, LoadLibraryelle provoque une erreur de protection chaque fois que le code référence ces données. (Les données d’étendue statique incluent des éléments statiques globaux et locaux.) C’est pourquoi, lorsque vous créez une DLL, vous devez éviter d’utiliser le stockage local thread. Si ce n’est pas le cas, informez vos utilisateurs DLL sur les pièges potentiels du chargement dynamique de votre DLL. Pour plus d’informations, consultez Utilisation du stockage local de thread dans une bibliothèque de liens dynamiques (SDK Windows) .

Comment utiliser la liaison implicite

Pour utiliser une DLL en liant implicitement, les exécutables clients doivent obtenir ces fichiers auprès du fournisseur de la DLL :

  • Un ou plusieurs fichiers d’en-tête (fichiers.h) qui contiennent les déclarations des classes de données, de fonctions et C++ exportées dans la DLL. Les classes, fonctions et données exportées par la DLL doivent toutes être marquées __declspec(dllimport) dans le fichier d’en-tête. Pour plus d’informations, consultez dllexport, dllimport.

  • Bibliothèque d’importation à lier à votre exécutable. L’éditeur de liens crée la bibliothèque d’importation lorsque la DLL est générée. Pour plus d’informations, consultez les fichiers LIB en tant qu’entrée de l’éditeur de liens.

  • Fichier DLL réel.

Pour utiliser les données, les fonctions et les classes d’une DLL en liant implicitement, tout fichier source client doit inclure les fichiers d’en-tête qui les déclarent. Du point de vue du codage, les appels aux fonctions exportées sont tout comme n’importe quel autre appel de fonction.

Pour générer le fichier exécutable client, vous devez établir un lien avec la bibliothèque d’importation de la DLL. Si vous utilisez un makefile externe ou un système de génération, spécifiez la bibliothèque d’importation avec les autres fichiers ou bibliothèques d’objets que vous liez.

Le système d’exploitation doit être en mesure de localiser le fichier DLL lorsqu’il charge l’exécutable appelant. Cela signifie que vous devez déployer ou vérifier l’existence de la DLL lorsque vous installez votre application.

Pour utiliser une DLL en liant explicitement, les applications doivent effectuer un appel de fonction pour charger explicitement la DLL au moment de l’exécution. Pour créer un lien explicite vers une DLL, une application doit :

  • Appelez LoadLibraryEx ou une fonction similaire pour charger la DLL et obtenir un handle de module.

  • Appelez GetProcAddress pour obtenir un pointeur de fonction vers chaque fonction exportée que l’application appelle. Étant donné que les applications appellent les fonctions DLL par le biais d’un pointeur, le compilateur ne génère pas de références externes. Il n’est donc pas nécessaire de lier une bibliothèque d’importation. Toutefois, vous devez disposer d’une ou using d’une typedef instruction qui définit la signature d’appel des fonctions exportées que vous appelez.

  • Appelez FreeLibrary quand vous avez terminé avec la DLL.

Par exemple, cet exemple de fonction appelle LoadLibrary pour charger une DLL nommée « MyDLL », appelle GetProcAddress un pointeur vers une fonction nommée « DLLFunc1 », appelle la fonction et enregistre le résultat, puis appelle FreeLibrary pour décharger la DLL.

#include "windows.h"

typedef HRESULT (CALLBACK* LPFNDLLFUNC1)(DWORD,UINT*);

HRESULT LoadAndCallSomeFunction(DWORD dwParam1, UINT * puParam2)
{
    HINSTANCE hDLL;               // Handle to DLL
    LPFNDLLFUNC1 lpfnDllFunc1;    // Function pointer
    HRESULT hrReturnVal;

    hDLL = LoadLibrary("MyDLL");
    if (NULL != hDLL)
    {
        lpfnDllFunc1 = (LPFNDLLFUNC1)GetProcAddress(hDLL, "DLLFunc1");
        if (NULL != lpfnDllFunc1)
        {
            // call the function
            hrReturnVal = lpfnDllFunc1(dwParam1, puParam2);
        }
        else
        {
            // report the error
            hrReturnVal = ERROR_DELAY_LOAD_FAILED;
        }
        FreeLibrary(hDLL);
    }
    else
    {
        hrReturnVal = ERROR_DELAY_LOAD_FAILED;
    }
    return hrReturnVal;
}

Contrairement à cet exemple, dans la plupart des cas, vous devez appeler LoadLibrary et FreeLibrary une seule fois dans votre application pour une DLL donnée. C’est particulièrement vrai si vous allez appeler plusieurs fonctions dans la DLL ou appeler des fonctions DLL à plusieurs reprises.

Sur quels éléments souhaitez-vous obtenir des informations supplémentaires ?

Voir aussi

Création de DLL C/C++ dans Visual Studio