Prise en charge de l’éditeur de liens pour les DLL à chargement différé

L’éditeur de liens MSVC prend en charge le chargement différé des DLL. Cette fonctionnalité vous permet d’utiliser les fonctions LoadLibrary du Kit de développement logiciel (SDK) Windows et GetProcAddress d’implémenter le chargement différé de DLL.

Sans chargement différé, la seule façon de charger une DLL au moment de l’exécution est d’utiliser LoadLibrary et GetProcAddress; le système d’exploitation charge la DLL lorsque l’exécutable ou la DLL qui l’utilise est chargé.

Avec une charge retardée, lorsque vous liez implicitement une DLL, l’éditeur de liens fournit des options pour retarder le chargement de la DLL jusqu’à ce que le programme appelle une fonction dans cette DLL.

Une application peut retarder le chargement d’une DLL à l’aide de l’option /DELAYLOAD d’éditeur de liens (Retarder l’importation de charge) avec une fonction d’assistance. (Une implémentation de fonction d’assistance par défaut est fournie par Microsoft.) La fonction d’assistance charge la DLL à la demande au moment de l’exécution en appelant LoadLibrary et GetProcAddress pour vous.

Envisagez de retarder le chargement d’une DLL si :

  • Votre programme n’appelle peut-être pas une fonction dans la DLL.

  • Une fonction dans la DLL peut ne pas être appelée jusqu’à la fin de l’exécution de votre programme.

Le chargement différé d’une DLL peut être spécifié pendant la génération d’un projet EXE ou DLL. Un projet DLL qui retarde le chargement d’une ou plusieurs DLL elle-même ne doit pas appeler un point d’entrée chargé de retard dans DllMain.

Spécifier des DLL pour retarder la charge

Vous pouvez spécifier les DLL à retarder le chargement à l’aide de l’option /delayload:dllname éditeur de liens. Si vous ne prévoyez pas d’utiliser votre propre version d’une fonction d’assistance, vous devez également lier votre programme ( delayimp.lib pour les applications de bureau) ou dloadhelper.lib (pour les applications UWP).

Voici un exemple simple de délai de chargement d’une DLL :

// cl t.cpp user32.lib delayimp.lib  /link /DELAYLOAD:user32.dll
#include <windows.h>
// uncomment these lines to remove .libs from command line
// #pragma comment(lib, "delayimp")
// #pragma comment(lib, "user32")

int main() {
   // user32.dll will load at this point
   MessageBox(NULL, "Hello", "Hello", MB_OK);
}

Générez la version DEBUG du projet. Parcourez le code à l’aide du débogueur et vous remarquerez qu’il user32.dll est chargé uniquement lorsque vous effectuez l’appel à MessageBox.

Décharger explicitement une DLL chargée de retard

L’option /delay:unload éditeur de liens permet à votre code de décharger explicitement une DLL qui a été chargée plus tard. Par défaut, les importations chargées par retard restent dans la table d’adresses d’importation (IAT). Toutefois, si vous utilisez /delay:unload sur la ligne de commande de l’éditeur de liens, la fonction d’assistance prend en charge le déchargement explicite de la DLL par un __FUnloadDelayLoadedDLL2 appel et réinitialise l’IAT à son formulaire d’origine. Les pointeurs non valides sont remplacés. L’IAT est un champ de la ImgDelayDescr structure qui contient l’adresse d’une copie de l’IAT d’origine, le cas échéant.

Exemple de déchargement d’une DLL chargée en retard

Cet exemple montre comment décharger explicitement une DLL, MyDll.dllqui contient une fonction fnMyDll:

// link with /link /DELAYLOAD:MyDLL.dll /DELAY:UNLOAD
#include <windows.h>
#include <delayimp.h>
#include "MyDll.h"
#include <stdio.h>

#pragma comment(lib, "delayimp")
#pragma comment(lib, "MyDll")
int main()
{
    BOOL TestReturn;
    // MyDLL.DLL will load at this point
    fnMyDll();

    //MyDLL.dll will unload at this point
    TestReturn = __FUnloadDelayLoadedDLL2("MyDll.dll");

    if (TestReturn)
        printf_s("\nDLL was unloaded");
    else
        printf_s("\nDLL was not unloaded");
}

Remarques importantes sur le déchargement d’une DLL chargée de retard :

  • Vous trouverez l’implémentation de la __FUnloadDelayLoadedDLL2 fonction dans le fichier delayhlp.cpp, dans le répertoire MSVC include . Pour plus d’informations, consultez Comprendre la fonction d’assistance au chargement différé.

  • Le name paramètre de la __FUnloadDelayLoadedDLL2 fonction doit correspondre exactement (y compris le cas) à ce que contient la bibliothèque d’importation. (Cette chaîne figure également dans la table d’importation dans l’image.) Vous pouvez afficher le contenu de la bibliothèque d’importation à l’aide DUMPBIN /DEPENDENTSde . Si vous préférez une correspondance de chaîne non sensible à la casse, vous pouvez effectuer une mise à jour __FUnloadDelayLoadedDLL2 pour utiliser l’une des fonctions de chaîne CRT non sensibles à la casse ou un appel d’API Windows.

Lier les importations chargées dans un délai

Le comportement de l’éditeur de liens par défaut consiste à créer une table d’adresses d’importation pouvant être liée (IAT) pour la DLL chargée de retard. Si la DLL est liée, la fonction d’assistance tente d’utiliser les informations liées au lieu d’appeler GetProcAddress sur chacune des importations référencées. Si l’horodatage ou l’adresse préférée ne correspond pas à celle de la DLL chargée, la fonction d’assistance suppose que la table d’adresses d’importation liée est obsolète. Elle se poursuit comme si l’IAT n’existe pas.

Si vous n’avez jamais l’intention de lier les importations chargées en retard d’une DLL, spécifiez /delay:nobind sur la ligne de commande de l’éditeur de liens. L’éditeur de liens ne génère pas la table d’adresses d’importation liée, ce qui permet d’économiser de l’espace dans le fichier image.

Charger toutes les importations pour une DLL chargée en retard

La __HrLoadAllImportsForDll fonction, définie dans delayhlp.cpp, indique à l’éditeur de liens de charger toutes les importations à partir d’une DLL spécifiée avec l’option /delayload éditeur de liens.

Lorsque vous chargez toutes les importations à la fois, vous pouvez centraliser la gestion des erreurs à un seul endroit. Vous pouvez éviter la gestion structurée des exceptions autour de tous les appels réels aux importations. Il évite également une situation dans laquelle votre application échoue en partie par le biais d’un processus : par exemple, si le code d’assistance ne parvient pas à charger une importation, après avoir correctement chargé d’autres utilisateurs.

L’appel __HrLoadAllImportsForDll ne modifie pas le comportement des hooks et de la gestion des erreurs. Pour plus d’informations, consultez Gestion et notification des erreurs.

__HrLoadAllImportsForDll fait une comparaison sensible à la casse avec le nom stocké à l’intérieur de la DLL elle-même.

Voici un exemple qui utilise __HrLoadAllImportsForDll dans une fonction appelée TryDelayLoadAllImports pour tenter de charger une DLL nommée. Il utilise une fonction, CheckDelayExceptionpour déterminer le comportement des exceptions.

int CheckDelayException(int exception_value)
{
    if (exception_value == VcppException(ERROR_SEVERITY_ERROR, ERROR_MOD_NOT_FOUND) ||
        exception_value == VcppException(ERROR_SEVERITY_ERROR, ERROR_PROC_NOT_FOUND))
    {
        // This example just executes the handler.
        return EXCEPTION_EXECUTE_HANDLER;
    }
    // Don't attempt to handle other errors
    return EXCEPTION_CONTINUE_SEARCH;
}

bool TryDelayLoadAllImports(LPCSTR szDll)
{
    __try
    {
        HRESULT hr = __HrLoadAllImportsForDll(szDll);
        if (FAILED(hr))
        {
            // printf_s("Failed to delay load functions from %s\n", szDll);
            return false;
        }
    }
    __except (CheckDelayException(GetExceptionCode()))
    {
        // printf_s("Delay load exception for %s\n", szDll);
        return false;
    }
    // printf_s("Delay load completed for %s\n", szDll);
    return true;
}

Vous pouvez utiliser le résultat de TryDelayLoadAllImports contrôler si vous appelez les fonctions d’importation ou non.

Gestion et notification des erreurs

Si votre programme utilise des DLL chargées en retard, il doit gérer les erreurs de manière robuste. Les échecs qui se produisent pendant l’exécution du programme entraînent des exceptions non gérées. Pour plus d’informations sur la gestion et la notification de chargement des retards dll, consultez Gestion et notification des erreurs.

Importation de délai de vidage chargée

Les importations chargées en retard peuvent être vidées à l’aide DUMPBIN /IMPORTSde . Ces importations apparaissent avec des informations légèrement différentes des importations standard. Ils sont séparés dans leur propre section de la /imports liste et sont explicitement étiquetés comme des importations chargées en retard. S’il existe des informations de déchargement présentes dans l’image, cela est noté. S’il existe des informations de liaison, l’heure et l’horodatage de la DLL cible sont notés avec les adresses liées des importations.

Contraintes sur les DLL de chargement différé

Il existe plusieurs contraintes sur le chargement différé des importations de DLL.

  • Les importations de données ne peuvent pas être prises en charge. Une solution de contournement consiste à gérer explicitement l’importation de données vous-même à l’aide (ou à l’aide LoadLibraryGetModuleHandle d’une fois que vous savez que l’assistance de chargement différé a chargé la DLL) et GetProcAddress.

  • Le chargement Kernel32.dll différé n’est pas pris en charge. Cette DLL doit être chargée pour que les routines d’assistance à chargement différé fonctionnent.

  • La liaison de points d’entrée transférés n’est pas prise en charge.

  • Un processus peut avoir un comportement différent si une DLL est chargée dans un délai, au lieu d’être chargée au démarrage. Il peut être vu s’il existe des initialisations par processus qui se produisent dans le point d’entrée de la DLL chargée par délai. D’autres cas incluent le protocole TLS statique (stockage local de thread), déclaré à l’aide __declspec(thread)de , qui n’est pas géré lorsque la DLL est chargée via LoadLibrary. Le stockage local des threads (TLS) de type dynamique, via TlsAlloc, TlsFree, TlsGetValue et TlsSetValue, peut encore être utilisé dans les bibliothèques DLL statiques ou chargées en différé.

  • Réinitialisez les pointeurs de fonction globale statiques vers des fonctions importées après le premier appel de chaque fonction. Cela est nécessaire, car la première utilisation d’un pointeur de fonction pointe vers le thunk, et non la fonction chargée.

  • Il n’existe actuellement aucun moyen de retarder le chargement de procédures spécifiques à partir d’une DLL lors de l’utilisation du mécanisme d’importation normal.

  • Les conventions d’appel personnalisées (telles que l’utilisation de codes de condition sur les architectures x86) ne sont pas prises en charge. En outre, les registres à virgule flottante ne sont enregistrés sur aucune plateforme. Veillez à ce que votre routine d’assistance personnalisée ou vos routines de crochet utilisent des types à virgule flottante : les routines doivent enregistrer et restaurer l’état complet à virgule flottante sur les machines qui utilisent des conventions d’appel d’inscription avec des paramètres à virgule flottante. Faites attention au chargement différé de la DLL CRT, en particulier si vous appelez des fonctions CRT qui prennent des paramètres à virgule flottante sur une pile de processeur de données numérique (NPD) dans la fonction d’aide.

Comprendre la fonction d’assistance au chargement différé

La fonction d’assistance pour le chargement différé pris en charge par l’éditeur de liens est ce qui charge réellement la DLL au moment de l’exécution. Vous pouvez modifier la fonction d’assistance pour personnaliser son comportement. Au lieu d’utiliser la fonction d’assistance fournie dans delayimp.lib, écrivez votre propre fonction et liez-la à votre programme. Une fonction d’assistance sert toutes les DLL chargées de retard. Pour plus d’informations, consultez Comprendre la fonction d’assistance au chargement différé et développer votre propre fonction d’assistance.

Voir aussi

Création de DLL C/C++ dans Visual Studio
Informations de référence sur l’éditeur de liens MSVC