DLL e comportamento delle librerie di runtime Visual C++
Quando si compila una libreria a collegamento dinamico (DLL) usando Visual Studio, per impostazione predefinita, il linker include la libreria di runtime di Visual C++ (VCRuntime). VCRuntime contiene il codice necessario per inizializzare e terminare un eseguibile C/C++. Se collegato a una DLL, il codice VCRuntime fornisce una funzione di punto di ingresso della DLL interna denominata _DllMainCRTStartup
che gestisce i messaggi del sistema operativo Windows alla DLL da collegare o scollegare da un processo o un thread. La _DllMainCRTStartup
funzione esegue attività essenziali, ad esempio la sicurezza del buffer dello stack, l'inizializzazione e la terminazione della libreria di runtime C (CRT) e le chiamate a costruttori e distruttori per oggetti statici e globali. _DllMainCRTStartup
chiama anche funzioni hook per altre librerie, ad esempio WinRT, MFC e ATL, per eseguire la propria inizializzazione e terminazione. Senza questa inizializzazione, CRT e altre librerie, nonché le variabili statiche, verrebbero lasciate in uno stato non inizializzato. Le stesse routine di inizializzazione e terminazione interne di VCRuntime vengono chiamate se la DLL usa una DLL CRT collegata in modo statico o una DLL CRT collegata dinamicamente.
Punto di ingresso DLL predefinito _DllMainCRTStartup
In Windows, tutte le DLL possono contenere una funzione di punto di ingresso facoltativa, in genere denominata , chiamata DllMain
sia per l'inizializzazione che per la terminazione. In questo modo è possibile allocare o rilasciare risorse aggiuntive in base alle esigenze. Windows chiama la funzione del punto di ingresso in quattro situazioni: collegamento del processo, scollegamento del processo, collegamento del thread e scollegamento del thread. Quando una DLL viene caricata in uno spazio indirizzi del processo, quando un'applicazione che la usa viene caricata o quando l'applicazione richiede la DLL in fase di esecuzione, il sistema operativo crea una copia separata dei dati della DLL. Questa operazione è denominata collegamento di processo. Il collegamento di thread si verifica quando il processo in cui viene caricata la DLL crea un nuovo thread. Lo scollegamento del thread si verifica quando il thread termina e lo scollegamento del processo è quando la DLL non è più necessaria e viene rilasciata da un'applicazione. Il sistema operativo effettua una chiamata separata al punto di ingresso della DLL per ognuno di questi eventi, passando un argomento motivo per ogni tipo di evento. Ad esempio, il sistema operativo invia DLL_PROCESS_ATTACH
come argomento reason per segnalare il collegamento del processo.
La libreria VCRuntime fornisce una funzione del punto di ingresso chiamata _DllMainCRTStartup
per gestire le operazioni di inizializzazione e terminazione predefinite. Al momento del collegamento del processo, la _DllMainCRTStartup
funzione configura i controlli di sicurezza del buffer, inizializza CRT e altre librerie, inizializza le informazioni sul tipo di runtime, inizializza e chiama costruttori per i dati statici e non locali, inizializza l'archiviazione locale del thread, incrementa un contatore statico interno per ogni collegamento e quindi chiama un utente o una libreria fornita.DllMain
In caso di scollegamento del processo, la funzione esegue questi passaggi inverso. DllMain
Chiama , decrementa il contatore interno, chiama distruttori, chiama funzioni di terminazione CRT e funzioni registrate atexit
e notifica qualsiasi altra libreria di terminazione. Quando il contatore degli allegati passa a zero, la funzione torna FALSE
a indicare a Windows che la DLL può essere scaricata. La _DllMainCRTStartup
funzione viene chiamata anche durante il collegamento del thread e lo scollegamento del thread. In questi casi, il codice VCRuntime non esegue alcuna inizializzazione o terminazione aggiuntiva autonomamente e chiama DllMain
semplicemente per passare il messaggio. Se DllMain
restituisce un risultato FALSE
dal collegamento di processo, segnala un errore, _DllMainCRTStartup
chiama DllMain
nuovamente e passa DLL_PROCESS_DETACH
come argomento motivo , quindi passa attraverso il resto del processo di terminazione.
Quando si compilano DLL in Visual Studio, il punto _DllMainCRTStartup
di ingresso predefinito fornito da VCRuntime viene collegato automaticamente. Non è necessario specificare una funzione del punto di ingresso per la DLL usando l'opzione linker /ENTRY (simbolo punto di ingresso).
Nota
Anche se è possibile specificare un'altra funzione punto di ingresso per una DLL usando l'opzione /ENTRY: linker, non è consigliabile, perché la funzione del punto di ingresso dovrà duplicare tutto ciò che _DllMainCRTStartup
fa, nello stesso ordine. VCRuntime fornisce funzioni che consentono di duplicarne il comportamento. Ad esempio, è possibile chiamare __security_init_cookie immediatamente sul collegamento del processo per supportare l'opzione di controllo del buffer /GS (controllo di sicurezza buffer). È possibile chiamare la _CRT_INIT
funzione, passando gli stessi parametri della funzione del punto di ingresso, per eseguire il resto delle funzioni di inizializzazione o terminazione della DLL.
Inizializzare una DLL
La DLL può avere codice di inizializzazione che deve essere eseguito quando la DLL viene caricata. Per poter eseguire funzioni di inizializzazione e terminazione della DLL personalizzate, _DllMainCRTStartup
chiama una funzione denominata DllMain
che è possibile fornire. La DllMain
firma deve essere necessaria per un punto di ingresso della DLL. La funzione _DllMainCRTStartup
del punto di ingresso predefinita chiama DllMain
usando gli stessi parametri passati da Windows. Per impostazione predefinita, se non si fornisce una DllMain
funzione, Visual Studio ne fornisce uno per l'utente e lo collega in in modo che _DllMainCRTStartup
abbia sempre qualcosa da chiamare. Ciò significa che se non è necessario inizializzare la DLL, non è necessario eseguire alcuna operazione speciale durante la compilazione della DLL.
Questa è la firma usata per 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
Alcune librerie esere il wrapping della DllMain
funzione. Ad esempio, in una NORMALE DLL MFC, implementare le CWinApp
funzioni membro e ExitInstance
dell'oggetto InitInstance
per eseguire l'inizializzazione e la terminazione richieste dalla DLL. Per altri dettagli, vedere la sezione Inizializzare dll MFC regolari.
Avviso
Esistono limiti significativi sulle operazioni che è possibile eseguire in modo sicuro in un punto di ingresso della DLL. Per altre informazioni su API Windows specifiche che non sono sicure da chiamare in DllMain
, vedere Procedure consigliate generali. Se è necessario un'inizializzazione qualsiasi ma la più semplice, eseguire questa operazione in una funzione di inizializzazione per la DLL. È possibile richiedere alle applicazioni di chiamare la funzione di inizializzazione dopo DllMain
l'esecuzione e prima di chiamare qualsiasi altra funzione nella DLL.
Inizializzare DLL normali (non MFC)
Per eseguire la propria inizializzazione in DLL normali (non MFC) che usano il punto di ingresso fornito da _DllMainCRTStartup
VCRuntime, il codice sorgente della DLL deve contenere una funzione denominata DllMain
. Il codice seguente presenta uno scheletro di base che mostra l'aspetto della definizione di 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
{
// 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.
}
Nota
La documentazione precedente di Windows SDK indica che il nome effettivo della funzione punto di ingresso della DLL deve essere specificato nella riga di comando del linker con l'opzione /ENTRY. Con Visual Studio non è necessario usare l'opzione /ENTRY se il nome della funzione del punto di ingresso è DllMain
. Infatti, se si usa l'opzione /ENTRY e si assegna un nome alla funzione del punto di ingresso diverso da DllMain
, il CRT non viene inizializzato correttamente a meno che la funzione del punto di ingresso non esegua le stesse chiamate di inizializzazione effettuate _DllMainCRTStartup
.
Inizializzare dll MFC regolari
Poiché le NORMALI DLL MFC hanno un CWinApp
oggetto, devono eseguire le attività di inizializzazione e terminazione nella stessa posizione di un'applicazione MFC: nelle InitInstance
funzioni membro e ExitInstance
della classe derivata dalla CWinApp
DLL. Poiché MFC fornisce una DllMain
funzione chiamata da _DllMainCRTStartup
per DLL_PROCESS_ATTACH
e DLL_PROCESS_DETACH
, non è consigliabile scrivere una funzione personalizzata DllMain
. La funzione fornita da DllMain
MFC chiama InitInstance
quando la DLL viene caricata e chiama ExitInstance
prima che la DLL venga scaricata.
Una normale DLL MFC può tenere traccia di più thread chiamando TlsAlloc e TlsGetValue nella relativa InitInstance
funzione. Queste funzioni consentono alla DLL di tenere traccia dei dati specifici del thread.
Nella DLL MFC regolare che collega dinamicamente a MFC, se si utilizza un supporto MFC OLE, MFC Database (o DAO) o MFC Sockets supportano rispettivamente la versione MFCOdell'estensione MFC di debug D.dll, la versioneMFCDD.dll e la versioneMFCND.dll (dove la versioneè il numero di versione) vengono collegate automaticamente. È necessario chiamare una delle funzioni di inizializzazione predefinite seguenti per ognuna di queste DLL in uso nella DLL CWinApp::InitInstance
MFC normale.
Tipo di supporto MFC | Funzione di inizializzazione da chiamare |
---|---|
MFC OLE (MFCOversioneD.dll) | AfxOleInitModule |
Database MFC (MFCDversioneD.dll) | AfxDbInitModule |
MFC Sockets (MFCNversioneD.dll) | AfxNetInitModule |
Inizializzare DLL di estensione MFC
Poiché le DLL dell'estensione MFC non dispongono di un CWinApp
oggetto derivato da (come fanno normali DLL MFC), è necessario aggiungere il codice di inizializzazione e terminazione alla DllMain
funzione generata dalla Creazione guidata DLL MFC.
La procedura guidata fornisce il codice seguente per le DLL dell'estensione MFC. Nel codice PROJNAME
è un segnaposto per il nome del progetto.
#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 creazione di un nuovo CDynLinkLibrary
oggetto durante l'inizializzazione consente alla DLL dell'estensione MFC di esportare CRuntimeClass
oggetti o risorse nell'applicazione client.
Se si intende usare la DLL dell'estensione MFC da una o più DLL MFC normali, è necessario esportare una funzione di inizializzazione che crea un CDynLinkLibrary
oggetto . Tale funzione deve essere chiamata da ognuna delle normali DLL MFC che usano la DLL dell'estensione MFC. Una posizione appropriata per chiamare questa funzione di inizializzazione è nella InitInstance
funzione membro dell'oggetto derivato dalla DLL MFC regolare prima di usare una delle classi o delle funzioni esportate della DLL dell'estensione CWinApp
MFC.
Nell'oggetto DllMain
generato dalla Creazione guidata DLL MFC, la chiamata a AfxInitExtensionModule
acquisisce le classi di runtime del modulo (CRuntimeClass
strutture) e le relative factory di oggetti (COleObjectFactory
oggetti) da usare quando viene creato l'oggetto CDynLinkLibrary
. È necessario controllare il valore restituito di AfxInitExtensionModule
; se viene restituito un valore zero da AfxInitExtensionModule
, restituire zero dalla DllMain
funzione.
Se la DLL dell'estensione MFC verrà collegata in modo esplicito a un eseguibile (ovvero le chiamate AfxLoadLibrary
eseguibili da collegare alla DLL), è necessario aggiungere una chiamata a AfxTermExtensionModule
su DLL_PROCESS_DETACH
. Questa funzione consente a MFC di pulire la DLL dell'estensione MFC quando ogni processo si disconnette dalla DLL dell'estensione MFC, che si verifica quando il processo viene chiuso o quando la DLL viene scaricata in seguito a una AfxFreeLibrary
chiamata. Se la DLL dell'estensione MFC verrà collegata in modo implicito all'applicazione, la chiamata a AfxTermExtensionModule
non è necessaria.
Le applicazioni che si collegano in modo esplicito alle DLL dell'estensione MFC devono chiamare AfxTermExtensionModule
quando si libera la DLL. Devono anche usare AfxLoadLibrary
e AfxFreeLibrary
(invece delle funzioni LoadLibrary
Win32 e FreeLibrary
) se l'applicazione usa più thread. L'uso AfxLoadLibrary
di e AfxFreeLibrary
garantisce che il codice di avvio e arresto eseguito quando la DLL dell'estensione MFC viene caricata e scaricata non danneggia lo stato MFC globale.
Poiché il MFCx0.dll viene completamente inizializzato dal tempo DllMain
chiamato, è possibile allocare memoria e chiamare funzioni MFC all'interno DllMain
(a differenza della versione a 16 bit di MFC).
Le DLL di estensione possono occuparsi del multithreading gestendo i DLL_THREAD_ATTACH
case e DLL_THREAD_DETACH
nella DllMain
funzione . Questi casi vengono passati a DllMain
quando i thread si collegano e si scollegano dalla DLL. La chiamata a TlsAlloc quando una DLL è collegata consente alla DLL di mantenere gli indici di archiviazione locale del thread (TLS) per ogni thread collegato alla DLL.
Si noti che il file di intestazione Afxdllx.h contiene definizioni speciali per le strutture usate nelle DLL di estensione MFC, ad esempio la definizione per AFX_EXTENSION_MODULE
e CDynLinkLibrary
. È consigliabile includere questo file di intestazione nella DLL dell'estensione MFC.
Nota
È importante non definire né annullare la _AFX_NO_XXX
definizione delle macro in pch.h (stdafx.h in Visual Studio 2017 e versioni precedenti). Queste macro esistono solo allo scopo di verificare se una determinata piattaforma di destinazione supporta o meno tale funzionalità. È possibile scrivere il programma per controllare queste macro ( ad esempio , #ifndef _AFX_NO_OLE_SUPPORT
), ma il programma non deve mai definire o annullare la definizione di queste macro.
Una funzione di inizializzazione di esempio che gestisce il multithreading è inclusa in Uso dell'archiviazione locale thread in una libreria a collegamento dinamico in Windows SDK. Si noti che l'esempio contiene una funzione del punto di ingresso denominata LibMain
, ma è necessario assegnare questa funzione DllMain
in modo che funzioni con le librerie MFC e C di runtime.
Vedi anche
Creare DLL C/C++ in Visual Studio
Punto di ingresso DllMain
Procedure consigliate per la libreria a collegamento dinamico