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 DllMainsia 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. DllMainChiama , 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 CWinAppDLL. 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 le DLL dell'estensione MFC MFCOversioneD.dll, MFCDversioneD.dll e MFCNversione D.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::InitInstanceMFC normale.

Tipo di supporto MFC Funzione di inizializzazione da chiamare
MFC OLE (MFCOversioneD.dll) AfxOleInitModule
Database MFC (MFCDversioneD.dll) AfxDbInitModule
Socket MFC (MFCNversioneD.dll) AfxNetInitModule

Inizializzare DLL di estensione MFC

Poiché le DLL dell'estensione MFC non dispongono di un CWinAppoggetto 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 CWinAppMFC.

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é MFCx0.dll è 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 Using Thread Local Archiviazione in a Dynamic-Link Library 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