DLL et comportement de la bibliothèque runtime Visual C++

Lorsque vous générez une bibliothèque de liens dynamiques (DLL) à l’aide de Visual Studio, par défaut, l’éditeur de liens inclut la bibliothèque d’exécution Visual C++ (VCRuntime). VCRuntime contient le code requis pour initialiser et mettre fin à un exécutable C/C++. Lorsqu’il est lié à une DLL, le code VCRuntime fournit une fonction interne de point d’entrée DLL appelée _DllMainCRTStartup qui gère les messages du système d’exploitation Windows à la DLL à attacher ou à détacher d’un processus ou d’un thread. La _DllMainCRTStartup fonction effectue des tâches essentielles telles que la configuration de la sécurité de la mémoire tampon de pile, l’initialisation et la terminaison de la bibliothèque runtime C (CRT), ainsi que les appels aux constructeurs et aux destructeurs pour les objets statiques et globaux. _DllMainCRTStartup appelle également des fonctions de raccordement pour d’autres bibliothèques telles que WinRT, MFC et ATL afin d’effectuer leur propre initialisation et arrêt. Sans cette initialisation, le CRT et d’autres bibliothèques, ainsi que vos variables statiques, sont laissés dans un état non initialisé. Les mêmes routines d’initialisation interne et de terminaison VCRuntime sont appelées si votre DLL utilise un CRT lié statiquement ou une DLL CRT liée dynamiquement.

Point d’entrée DLL par défaut _DllMainCRTStartup

Dans Windows, toutes les DLL peuvent contenir une fonction de point d’entrée facultative, généralement appelée DllMain, qui est appelée à la fois pour l’initialisation et l’arrêt. Cela vous donne la possibilité d’allouer ou de libérer des ressources supplémentaires en fonction des besoins. Windows appelle la fonction de point d’entrée dans quatre situations : attachement de processus, détachement de processus, attachement de thread et détachement de thread. Lorsqu’une DLL est chargée dans un espace d’adressage de processus, soit lorsqu’une application qui l’utilise soit chargée, soit lorsque l’application demande la DLL au moment de l’exécution, le système d’exploitation crée une copie distincte des données DLL. Il s’agit de l’attachement de processus. L’attachement de thread se produit lorsque le processus dans lequel la DLL est chargée crée un thread. Le détachement de thread se produit lorsque le thread se termine et que le processus se détache lorsque la DLL n’est plus nécessaire et est libérée par une application. Le système d’exploitation effectue un appel distinct au point d’entrée DLL pour chacun de ces événements, en passant un argument de raison pour chaque type d’événement. Par exemple, le système d’exploitation envoie DLL_PROCESS_ATTACH comme argument de raison pour signaler l’attachement de processus.

La bibliothèque VCRuntime fournit une fonction de point d’entrée appelée _DllMainCRTStartup pour gérer les opérations d’initialisation et de terminaison par défaut. Lors de l’attachement de processus, la _DllMainCRTStartup fonction configure des case activée de sécurité de mémoire tampon, initialise le CRT et d’autres bibliothèques, initialise les informations de type d’exécution, initialise et appelle les constructeurs pour les données statiques et non locales, initialise le stockage thread-local, incrémente un compteur statique interne pour chaque attachement, puis appelle un utilisateur ou une bibliothèque fourniDllMain. Lors du détachement du processus, la fonction passe par ces étapes en sens inverse. Il appelle DllMain, décrémente le compteur interne, appelle les destructeurs, appelle les fonctions de terminaison CRT et les fonctions inscrites atexit , et notifie toutes les autres bibliothèques de terminaison. Lorsque le compteur de pièces jointes est égal à zéro, la fonction retourne FALSE pour indiquer à Windows que la DLL peut être déchargée. La _DllMainCRTStartup fonction est également appelée pendant l’attachement de thread et le détachement de thread. Dans ces cas, le code VCRuntime n’effectue aucune initialisation ou arrêt supplémentaire par lui-même, et il suffit d’appeler DllMain pour transmettre le message. Si DllMain un retour FALSE à partir de l’attachement de processus, signalant l’échec, _DllMainCRTStartup appelle DllMain à nouveau et passe DLL_PROCESS_DETACH en tant qu’argument de raison , passe par le reste du processus d’arrêt.

Lors de la génération de DLL dans Visual Studio, le point _DllMainCRTStartup d’entrée par défaut fourni par VCRuntime est lié automatiquement. Vous n’avez pas besoin de spécifier une fonction de point d’entrée pour votre DLL à l’aide de l’option de l’éditeur de liens /ENTRY (symbole de point d’entrée).

Remarque

Bien qu’il soit possible de spécifier une autre fonction de point d’entrée pour une DLL à l’aide de l’option /ENTRY : linker, nous ne le recommandons pas, car votre fonction de point d’entrée doit dupliquer tout ce qui _DllMainCRTStartup le fait, dans le même ordre. VCRuntime fournit des fonctions qui vous permettent de dupliquer son comportement. Par exemple, vous pouvez appeler __security_init_cookie immédiatement sur l’attachement de processus pour prendre en charge l’option de mise en mémoire tampon /GS (case activée case activée de sécurité de la mémoire tampon). Vous pouvez appeler la _CRT_INIT fonction, en passant les mêmes paramètres que la fonction de point d’entrée, pour effectuer le reste des fonctions d’initialisation ou de terminaison dll.

Initialiser une DLL

Votre DLL peut avoir du code d’initialisation qui doit s’exécuter lorsque votre DLL se charge. Pour que vous puissiez effectuer vos propres fonctions d’initialisation et de terminaison DLL, _DllMainCRTStartup appelez une fonction appelée DllMain que vous pouvez fournir. Votre DllMain signature doit être requise pour un point d’entrée DLL. La fonction _DllMainCRTStartup de point d’entrée par défaut appelle DllMain à l’aide des mêmes paramètres passés par Windows. Par défaut, si vous ne fournissez pas de DllMain fonction, Visual Studio en fournit un pour vous et le lie afin qu’il _DllMainCRTStartup ait toujours quelque chose à appeler. Cela signifie que si vous n’avez pas besoin d’initialiser votre DLL, il n’y a rien de spécial à faire lors de la génération de votre DLL.

Il s’agit de la signature utilisée pour DllMain:

#include <windows.h>

extern "C" BOOL WINAPI DllMain (
    HINSTANCE const instance,  // handle to DLL module
    DWORD     const reason,    // reason for calling function
    LPVOID    const reserved); // reserved

Certaines bibliothèques encapsulent la DllMain fonction pour vous. Par exemple, dans une DLL MFC standard, implémentez les CWinApp fonctions membres et ExitInstance de l’objet InitInstance pour effectuer l’initialisation et l’arrêt requis par votre DLL. Pour plus d’informations, consultez la section Initialiser les DLL MFC standard.

Avertissement

Il existe des limites significatives sur ce que vous pouvez faire en toute sécurité dans un point d’entrée DLL. Pour plus d’informations sur des API Windows spécifiques qui ne sont pas sécurisées pour appeler DllMain, consultez Les meilleures pratiques générales. Si vous avez besoin de quelque chose, mais que l’initialisation la plus simple, effectuez cela dans une fonction d’initialisation pour la DLL. Vous pouvez exiger que les applications appellent la fonction d’initialisation après DllMain l’exécution et avant qu’elles n’appellent d’autres fonctions dans la DLL.

Initialiser des DLL ordinaires (non MFC)

Pour effectuer votre propre initialisation dans des DLL ordinaires (non MFC) qui utilisent le point d’entrée fourni par _DllMainCRTStartup VCRuntime, votre code source DLL doit contenir une fonction appelée DllMain. Le code suivant présente un squelette de base montrant la définition de DllMain ce qui peut ressembler à ceci :

#include <windows.h>

extern "C" BOOL WINAPI DllMain (
    HINSTANCE const instance,  // handle to DLL module
    DWORD     const reason,    // reason for calling function
    LPVOID    const reserved)  // reserved
{
    // Perform actions based on the reason for calling.
    switch (reason)
    {
    case DLL_PROCESS_ATTACH:
        // Initialize once for each new process.
        // Return FALSE to fail DLL load.
        break;

    case DLL_THREAD_ATTACH:
        // Do thread-specific initialization.
        break;

    case DLL_THREAD_DETACH:
        // Do thread-specific cleanup.
        break;

    case DLL_PROCESS_DETACH:
        // Perform any necessary cleanup.
        break;
    }
    return TRUE;  // Successful DLL_PROCESS_ATTACH.
}

Remarque

L’ancienne documentation du Kit de développement logiciel (SDK) Windows indique que le nom réel de la fonction de point d’entrée DLL doit être spécifié sur la ligne de commande de l’éditeur de liens avec l’option /ENTRY. Avec Visual Studio, vous n’avez pas besoin d’utiliser l’option /ENTRY si le nom de votre fonction de point d’entrée est DllMain. En fait, si vous utilisez l’option /ENTRY et nommez votre fonction de point d’entrée autre que DllMain, le CRT n’est pas initialisé correctement, sauf si votre fonction de point d’entrée effectue les mêmes appels d’initialisation qui _DllMainCRTStartup effectuent.

Initialiser des DLL MFC régulières

Étant donné que les DLL MFC standard ont un CWinApp objet, elles doivent effectuer leurs tâches d’initialisation et de terminaison dans le même emplacement qu’une application MFC : dans les InitInstance fonctions membres de ExitInstance la classe dérivée de CWinAppla DLL. Étant donné que MFC fournit une DllMain fonction appelée par _DllMainCRTStartupDLL_PROCESS_ATTACH et DLL_PROCESS_DETACHque vous ne devez pas écrire votre propre DllMain fonction. Les appels InitInstance de fonction fournis par DllMain MFC lorsque votre DLL est chargée et qu’elle appelle ExitInstance avant le déchargement de la DLL.

Une DLL MFC standard peut effectuer le suivi de plusieurs threads en appelant TlsAlloc et TlsGetValue dans sa InitInstance fonction. Ces fonctions permettent à la DLL de suivre les données spécifiques au thread.

Dans votre DLL MFC standard qui lie dynamiquement MFC à MFC, si vous utilisez n’importe quelle base de données MFC, base de données MFC (ou DAO) ou prise en charge des sockets MFC, respectivement, les dll DLLs d’extension MFC de débogage MFCOversion D.dll, MFCDversionD.dll et MFCNversionD.dll (où la version est le numéro de version) sont liées automatiquement. Vous devez appeler l’une des fonctions d’initialisation CWinApp::InitInstanceprédéfinies suivantes pour chacune de ces DLL que vous utilisez dans la DLL MFC standard.

Type de prise en charge de MFC Fonction d’initialisation à appeler
OLE MFC (MFCOversionD.dll) AfxOleInitModule
Base de données MFC (versionMFCDD.dll) AfxDbInitModule
Sockets MFC (MFCNversionD.dll) AfxNetInitModule

Initialiser des DLL d’extension MFC

Étant donné que les DLL d’extension MFC n’ont pas d’objet CWinAppdérivé (comme les DLL MFC standard), vous devez ajouter votre code d’initialisation et de terminaison à la DllMain fonction générée par l’Assistant DLL MFC.

L’Assistant fournit le code suivant pour les DLL d’extension MFC. Dans le code, PROJNAME il s’agit d’un espace réservé pour le nom de votre projet.

#include "pch.h" // For Visual Studio 2017 and earlier, use "stdafx.h"
#include <afxdllx.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
static AFX_EXTENSION_MODULE PROJNAMEDLL;

extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
   if (dwReason == DLL_PROCESS_ATTACH)
   {
      TRACE0("PROJNAME.DLL Initializing!\n");

      // MFC extension DLL one-time initialization
      AfxInitExtensionModule(PROJNAMEDLL,
                                 hInstance);

      // Insert this DLL into the resource chain
      new CDynLinkLibrary(Dll3DLL);
   }
   else if (dwReason == DLL_PROCESS_DETACH)
   {
      TRACE0("PROJNAME.DLL Terminating!\n");
   }
   return 1;   // ok
}

La création d’un CDynLinkLibrary objet pendant l’initialisation permet à la DLL d’extension MFC d’exporter des CRuntimeClass objets ou des ressources vers l’application cliente.

Si vous allez utiliser votre DLL d’extension MFC à partir d’une ou plusieurs DLL MFC standard, vous devez exporter une fonction d’initialisation qui crée un CDynLinkLibrary objet. Cette fonction doit être appelée à partir de chacune des DLL MFC standard qui utilisent la DLL d’extension MFC. Un emplacement approprié pour appeler cette fonction d’initialisation se trouve dans la InitInstance fonction membre de l’objet dérivé de CWinAppla DLL MFC standard avant d’utiliser l’une des classes ou fonctions exportées de la DLL d’extension MFC.

Dans l’Assistant DllMain DLL MFC généré, l’appel pour AfxInitExtensionModule capturer les classes d’exécution (CRuntimeClass structures) du module ainsi que ses fabriques d’objets (COleObjectFactory objets) à utiliser lors de la création de l’objet CDynLinkLibrary . Vous devez case activée la valeur de retour de AfxInitExtensionModule; si une valeur zéro est retournée par AfxInitExtensionModule, retournez zéro de votre DllMain fonction.

Si votre DLL d’extension MFC est explicitement liée à un exécutable (ce qui signifie que les appels AfxLoadLibrary exécutables à lier à la DLL), vous devez ajouter un appel sur AfxTermExtensionModuleDLL_PROCESS_DETACH. Cette fonction permet à MFC de propre la DLL d’extension MFC lorsque chaque processus se détache de la DLL d’extension MFC (qui se produit lorsque le processus se ferme ou lorsque la DLL est déchargée à la suite d’un AfxFreeLibrary appel). Si votre DLL d’extension MFC est liée implicitement à l’application, l’appel à AfxTermExtensionModule n’est pas nécessaire.

Les applications qui relient explicitement les DLL d’extension MFC doivent appeler AfxTermExtensionModule lors de la libération de la DLL. Ils doivent également utiliser AfxLoadLibrary et AfxFreeLibrary (au lieu des fonctions LoadLibrary Win32 et FreeLibrary) si l’application utilise plusieurs threads. L’utilisation AfxLoadLibrary et AfxFreeLibrary la garantie que le code de démarrage et d’arrêt qui s’exécute lorsque la DLL d’extension MFC est chargée et déchargée n’endommage pas l’état MFC global.

Étant donné que MFCx0.dll est entièrement initialisé au moment DllMain de l’appel, vous pouvez allouer de la mémoire et appeler des fonctions MFC dans DllMain (contrairement à la version 16 bits de MFC).

Les DLL d’extension peuvent prendre en charge la multithreading en gérant les cas et DLL_THREAD_DETACH les DLL_THREAD_ATTACH cas dans la DllMain fonction. Ces cas sont passés au moment où DllMain les threads attachent et détachent de la DLL. L’appel de TlsAlloc lorsqu’une DLL est attachée permet à la DLL de gérer les index de stockage local de thread (TLS) pour chaque thread attaché à la DLL.

Notez que le fichier d’en-tête Afxdllx.h contient des définitions spéciales pour les structures utilisées dans les DLL d’extension MFC, telles que la définition pour AFX_EXTENSION_MODULE et CDynLinkLibrary. Vous devez inclure ce fichier d’en-tête dans votre DLL d’extension MFC.

Remarque

Il est important que vous ne définissiez ni ne dédefiniez aucune des _AFX_NO_XXX macros dans pch.h (stdafx.h dans Visual Studio 2017 et versions antérieures). Ces macros existent uniquement à des fins de case activée si une plateforme cible particulière prend en charge cette fonctionnalité ou non. Vous pouvez écrire votre programme pour case activée ces macros (par exemple), #ifndef _AFX_NO_OLE_SUPPORTmais votre programme ne doit jamais définir ou annuler la définition de ces macros.

Un exemple de fonction d’initialisation qui gère le multithreading est inclus dans l’utilisation d’un Stockage local thread dans une bibliothèque de liens dynamiques dans le Kit de développement logiciel (SDK) Windows. Notez que l’exemple contient une fonction de point d’entrée appelée LibMain, mais vous devez nommer cette fonction DllMain afin qu’elle fonctionne avec les bibliothèques MFC et C runtime.

Voir aussi

Création de DLL C/C++ dans Visual Studio
Point d’entrée DllMain
Meilleures pratiques en matière de bibliothèque de liens dynamiques