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 vérifications 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 local de threads, incrémente un compteur statique interne pour chaque attachement, puis appelle un utilisateur ou une bibliothèque fourni DllMain
. 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 vérification de la mémoire tampon /GS (vérification de la 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 CWinApp
la DLL. Étant donné que MFC fournit une DllMain
fonction appelée par _DllMainCRTStartup
DLL_PROCESS_ATTACH
et DLL_PROCESS_DETACH
que 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 d’extension MFC déboguent la version MFCOD.dll, la versionMFCDD.dll et la versionMFCND.dll (où la version est le numéro de version) sont liées automatiquement. Vous devez appeler l’une des fonctions d’initialisation CWinApp::InitInstance
pré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 (versionMFCOD.dll) | AfxOleInitModule |
Base de données MFC (versionMFCDD.dll) | AfxDbInitModule |
Sockets MFC (versionMFCND.dll) | AfxNetInitModule |
Initialiser des DLL d’extension MFC
Étant donné que les DLL d’extension MFC n’ont pas d’objet CWinApp
dé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 CWinApp
la 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 vérifier 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 AfxTermExtensionModule
DLL_PROCESS_DETACH
. Cette fonction permet à MFC de nettoyer 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 le MFCx0.dll est entièrement initialisé au moment DllMain
de l’appel, vous pouvez allouer de la mémoire et appeler des fonctions MFC au sein DllMain
de (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 pour vérifier si une plateforme cible particulière prend en charge cette fonctionnalité ou non. Vous pouvez écrire votre programme pour vérifier ces macros (par exemple), #ifndef _AFX_NO_OLE_SUPPORT
mais votre programme ne doit jamais définir ou annuler la définition de ces macros.
Un exemple de fonction d’initialisation qui gère la multithreading est inclus dans l’utilisation du 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