Verhalten der Laufzeitbibliothek für DLLs und Visual C++

Wenn Sie eine DLL (Dynamic Link Library) mithilfe von Visual Studio erstellen, enthält der Linker standardmäßig die Visual C++-Laufzeitbibliothek (VCRuntime). Die VCRuntime-Bibliothek enthält Code, der zum Initialisieren und Beenden einer ausführbaren C-/C++-Datei benötigt wird. Wenn eine Verknüpfung mit einer DLL besteht, stellt der VCRuntime-Code eine interne DLL-Einstiegspunktfunktion namens _DllMainCRTStartup bereit, die Windows-Betriebssystemnachrichten an die DLL verarbeitet, um einen Prozess oder Thread anzufügen oder zu trennen. Die _DllMainCRTStartup-Funktion führt wichtige Aufgaben aus, z. B. das Einrichten der Stapelpuffersicherheit, das Initialisieren und Beenden der C-Laufzeitbibliothek (CRT) und das Aufrufen von Konstruktoren und Destruktoren für statische und globale Objekte. _DllMainCRTStartup ruft auch Hookfunktionen für andere Bibliotheken wie WinRT, MFC und ATL auf, um eine eigene Initialisierung und Beendigung durchzuführen. Ohne diese Initialisierung verbleiben die C-Laufzeitbibliothek und andere Bibliotheken sowie Ihre statischen Variablen in einem nicht initialisierten Zustand. Unabhängig davon, ob Ihre DLL eine statisch verknüpfte C-Laufzeitbibliothek oder eine dynamisch verknüpfte C-Laufzeitbibliothek-DLL verwendet, werden die gleichen internen Initialisierungs- und Beendigungsroutinen der VCRuntime-Bibliothek verwendet.

DLL-Standardeinstiegspunkt „_DllMainCRTStartup“

In Windows können alle DLLs eine optionale Einstiegspunktfunktion (in der Regel DllMain) enthalten, die sowohl für die Initialisierung als auch für die Beendigung aufgerufen wird. Dadurch haben Sie die Möglichkeit, zusätzliche Ressourcen je nach Bedarf zu reservieren oder freizugeben. Windows ruft die Einstiegspunktfunktion in den folgenden vier Situationen auf: Anfügen an bzw. Trennen von einem Prozess und Anfügen an einen bzw. Trennen von einem Thread. Sobald eine DLL in einen Prozessadressbereich geladen wird, entweder wenn eine Anwendung geladen wird, die sie verwendet, oder wenn die Anwendung die DLL zur Laufzeit anfordert, erstellt das Betriebssystem eine separate Kopie der DLL-Daten. Dies wird als Prozessanfügung bezeichnet. Die Threadanfügung erfolgt, wenn der Prozess, in den die DLL geladen wird, einen neuen Thread erstellt. Die Threadtrennung tritt auf, wenn der Thread beendet wird. Die Prozesstrennung tritt auf, wenn die DLL nicht mehr benötigt wird und von einer Anwendung freigegeben wird. Das Betriebssystem führt einen separaten Aufruf des DLL-Einstiegspunkts für jedes dieser Ereignisse durch und übergibt ein reason-Argument für jeden Ereignistyp. Das Betriebssystem sendet beispielsweise DLL_PROCESS_ATTACH als reason-Argument, um die Prozessanfügung zu signalisieren.

Die VCRuntime-Bibliothek stellt eine Einstiegspunktfunktion namens _DllMainCRTStartup bereit, um Standardinitialisierungsvorgänge und -beendigungsvorgänge zu verarbeiten. Bei der Prozessanfügung richtet die _DllMainCRTStartup-Funktion Puffersicherheitsüberprüfungen ein, initialisiert die C-Laufzeitbibliothek und andere Bibliotheken, initialisiert die Laufzeittypinformationen, initialisiert die Aufrufkonstruktoren für statische und nicht lokale Daten, initialisiert den lokalen Threadspeicher, inkrementiert einen internen statischen Zähler für jede Anfügung und ruft dann eine von einem Benutzer oder einer Bibliothek bereitgestellte DllMain-Funktion auf. Bei Prozesstrennung, durchläuft die Funktion diese Schritte in umgekehrter Reihenfolge. DllMain wird aufgerufen, der interne Zähler wird reduziert, Destruktoren werden aufgerufen, Beendigungsfunktionen werden für die C-Laufzeitbibliothek aufgerufen, registrierte atexit-Funktionen werden aufgerufen und alle anderen Bibliotheken werden über die Beendigung informiert. Wenn der Anfügungszähler auf null (0) fällt, gibt die Funktion FALSE zurück, um Windows darüber zu informieren, dass die DLL entladen werden kann. Die _DllMainCRTStartup-Funktion wird ebenfalls während der Threadanfügung und -trennung aufgerufen. In diesen Fällen führt der VCRuntime-Code keine zusätzliche Initialisierung oder Beendigung eigenständig durch und ruft lediglich DllMain auf, um die Nachricht weiterzuleiten. Wenn DllMain den Wert FALSE von der Prozessanfügung zurückgibt, um einen Fehler anzugeben, ruft _DllMainCRTStartup wieder DllMain auf, übergibt DLL_PROCESS_DETACH als reason-Argument und durchläuft dann den Rest des Beendigungsprozesses.

Beim Erstellen von DLLs in Visual Studio wird der von VCRuntime bereitgestellte Standardeinstiegspunkt _DllMainCRTStartup automatisch verknüpft. Sie müssen keine Einstiegspunktfunktion für Ihre DLL mit der Linkeroption /ENTRY (Einstiegspunktsymbol) festlegen.

Hinweis

Es ist zwar möglich, eine andere Einstiegspunktfunktion für eine DLL mit der Linkeroption „/ENTRY“ festzulegen, aber davon wird abgeraten, da Ihre Einstiegspunktfunktion alle Aktionen von _DllMainCRTStartup duplizieren in derselben Reihenfolge müsste. Die VCRuntime-Bibliothek stellt Funktionen bereit, mit denen Sie das Verhalten duplizieren können. Beispielsweise können Sie __security_init_cookie direkt bei der Prozessanfügung aufrufen, um die Pufferüberprüfungsoption /GS (Puffersicherheitsüberprüfung) zu unterstützen. Sie können die _CRT_INIT-Funktion aufrufen und dieselben Parameter als Einstiegspunktfunktion übergeben, um die restlichen DLL-Initialisierungs- oder -Beendigungsfunktionen auszuführen.

Initialisieren einer DLL

Ihre DLL verfügt möglicherweise über Initialisierungscode, der beim Laden der DLL ausgeführt werden muss. Damit Sie Ihre eigenen DLL-Initialisierungs- und -Beendigungsfunktionen ausführen können, ruft _DllMainCRTStartup eine Funktion namens DllMain auf, die Sie bereitstellen können. Ihre DllMain-Funktion muss über die für einen DLL-Einstiegspunkt erforderliche Signatur verfügen. Die Standard-Einstiegspunktfunktion _DllMainCRTStartup ruft DllMain mit denselben Parametern auf, die von Windows übergeben werden. Wenn Sie keine DllMain-Funktion angeben, stellt Visual Studio standardmäßig eine bereit und verknüpft diese, sodass _DllMainCRTStartup immer eine Funktion aufrufen kann. Das bedeutet, dass Sie beim Erstellen Ihrer DLL keine weiteren Schritte ausführen müssen, wenn Sie Ihre DLL nicht initialisieren müssen.

Die folgende Signatur wird für DllMain verwendet:

#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

Einige Bibliotheken umschließen die DllMain-Funktion für Sie. Implementieren Sie in einer regulärer MFC-DLL beispielsweise die Memberfunktionen InitInstance und ExitInstance des CWinApp-Objekts, um die für Ihre DLL erforderliche Initialisierung und Beendigung durchzuführen. Weitere Informationen finden Sie im Abschnitt Initialisieren regulärer MFC-DLLs.

Warnung

Es gibt erhebliche Einschränkungen bei den Aktionen, die Sie sicher in einem DLL-Einstiegspunkt durchführen können. Weitere Informationen zu bestimmten Windows-APIs, die nicht sicher zum Aufrufen DllMainsind, finden Sie unter "Allgemeine Bewährte Methoden". Wenn Sie mehr als nur die einfachste Initialisierung durchführen müssen, führen Sie dies in einer Initialisierungsfunktion für die DLL durch. Sie können erfordern, dass Anwendungen die Initialisierungsfunktion aufrufen, nachdem DllMain ausgeführt wurde und bevor sie andere Funktionen in der DLL aufrufen.

Initialisieren herkömmlicher DLLs (nicht MFC)

Ihr DLL-Quellcode muss eine Funktion namens DllMain enthalten, damit Sie Ihre eigene Initialisierung in herkömmlichen DLLs (nicht MFC) durchführen können, die den von der VCRuntime-Bibliothek bereitgestellten _DllMainCRTStartup-Einstiegspunkt verwenden. Der folgende Code stellt ein Grundgerüst dar, das veranschaulicht, wie die Definition von DllMain aussehen könnte:

#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.
}

Hinweis

Laut älterer Dokumentation zum Windows SDK muss der tatsächliche Name der DLL-Einstiegspunktfunktion für die Linkerbefehlszeile mit der Option „/ENTRY“ festgelegt werden. In Visual Studio müssen Sie die Option „/ENTRY“ nicht verwenden, wenn der Name Ihrer Einstiegspunktfunktion DllMain ist. Tatsächlich wird die C-Laufzeitbibliothek nicht ordnungsgemäß initialisiert, wenn Sie die Option „/ENTRY“ verwenden und Ihrer Einstiegspunktfunktion einen anderen Namen als DllMain geben, es sei denn, Ihre Einstiegspunktfunktion führt die selben Initialisierungsaufrufe aus, die _DllMainCRTStartup ausführt.

Initialisieren regulärer MFC-DLLs

Da reguläre MFC-DLLs ein CWinApp-Objekt enthalten, sollten sie ihre Initialisierungs- und Beendigungsaufgaben am selben Ort wie eine MFC-Anwendung durchführen: in den Memberfunktionen InitInstance und ExitInstance der von CWinApp abgeleiteten Klasse der DLL. Da MFC eine DllMain-Funktion bereitstellt, die von _DllMainCRTStartup für DLL_PROCESS_ATTACH und DLL_PROCESS_DETACH aufgerufen wird, sollten Sie nicht Ihre eigene DllMain-Funktion schreiben. Die von MFC bereitgestellte DllMain-Funktion ruft InitInstance auf, wenn Ihre DLL geladen wird, und ruft ExitInstance auf, bevor die DLL entladen wird.

Eine reguläre MFC-DLL kann mehrere Threads nachverfolgen, indem TlsAlloc und TlsGetValue in der InitInstance-Funktion aufgerufen werden. Diese Funktionen ermöglichen der DLL die Nachverfolgung von threadspezifischen Daten.

In Ihrer regulären MFC-DLL, die dynamisch mit MFC verknüpft wird, wenn Sie MFC-Unterstützung für OLE, Datenbanken (oder DAO) oder Sockets verwenden, werden die MFC-Erweiterungs-DLLs „MFCOversionD.dll“, „MFCDversionD.dll“ und „MFCNversionD.dll“ (wobei version der Versionsnummer entspricht) automatisch verknüpft. Sie müssen eine der folgenden vordefinierten Initialisierungsfunktionen für jede dieser DLLs aufrufen, die Sie in der CWinApp::InitInstance-Methode Ihrer regulären MFC-DLL verwenden.

Typ der MFC-Unterstützung Aufzurufende Initialisierungsfunktion
MFC-OLE (MFCOversionD.dll) AfxOleInitModule
MFC-Datenbank (MFCDversionD.dll) AfxDbInitModule
MFC-Sockets (MFCNversionD.dll) AfxNetInitModule

Initialisieren von MFC-Erweiterungs-DLLs

Da MFC-Erweiterungs-DLLs über kein von CWinApp abgeleitetes Objekt verfügen (wie reguläre MFC-DLLs), sollten Sie Ihren Initialisierungs- und Beendigungscode zur DllMain-Funktion hinzufügen, die der MFC-DLL-Assistent generiert.

Der Assistent stellt den folgenden Code für MFC-Erweiterungs-DLLs bereit. Im Code ist PROJNAME ein Platzhalter für den Namen Ihres Projekts.

#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
}

Durch die Erstellung eines neuen CDynLinkLibrary-Objekts während der Initialisierung kann die MFC-Erweiterungs-DLL CRuntimeClass-Objekte oder Ressourcen in die Clientanwendung exportieren.

Wenn Sie Ihre MFC-Erweiterungs-DLL über mindestens eine reguläre MFC-DLL verwenden, müssen Sie eine Initialisierungsfunktion exportieren, die ein CDynLinkLibrary-Objekt erstellt. Die Funktion muss von allen regulären MFC-DLLs aufgerufen werden, die die MFC-Erweiterungs-DLL verwenden. Die InitInstance-Memberfunktion des von CWinApp abgeleiteten Objekts der regulären MFC-DLL bietet sich dafür an, diese Initialisierungsfunktion aufzurufen, bevor andere exportierte Klassen oder Funktionen der MFC-Erweiterungs-DLL verwendet werden.

In der DllMain-Funktion, die der MFC-DLL-Assistent generiert, erfasst der Aufruf von AfxInitExtensionModule die Laufzeitklassen des Moduls (CRuntimeClass-Strukturen) sowie die Objektfactorys (COleObjectFactory-Objekte), die beim Erstellen des CDynLinkLibrary-Objekts verwendet werden. Sie sollten den Rückgabewert von AfxInitExtensionModule überprüfen. Wenn AfxInitExtensionModule den Wert null (0) zurückgibt, geben Sie null in Ihrer DllMain-Funktion zurück.

Wenn Ihre MFC-Erweiterungs-DLL explizit mit einer ausführbaren Datei verknüpft wird (d. h. die ausführbare Datei ruft AfxLoadLibrary für die Verknüpfung mit der DLL auf), sollten Sie einen Aufruf von AfxTermExtensionModule bei DLL_PROCESS_DETACH hinzufügen. Mithilfe dieser Funktion kann MFC die MFC-Erweiterungs-DLL bereinigen, wenn einzelne Prozesse von der MFC-Erweiterungs-DLL getrennt werden (dies geschieht, wenn der Prozess beendet wird oder die DLL aufgrund eines AfxFreeLibrary-Aufrufs entladen wird). Wenn Ihre MFC-Erweiterungs-DLL implizit mit der Anwendung verknüpft wird, ist der Aufruf von AfxTermExtensionModule nicht erforderlich.

Anwendungen, die explizit mit MFC-Erweiterungs-DLLs verknüpft werden, müssen AfxTermExtensionModule beim Freigeben der DLL aufrufen. Außerdem sollten sie AfxLoadLibrary und AfxFreeLibrary (anstelle der Win32-Funktionen LoadLibrary und FreeLibrary) verwenden, wenn die Anwendung mehrere Threads nutzt. Mit AfxLoadLibrary und AfxFreeLibrary wird sichergestellt, dass der Start- und Beendigungscode nicht den globalen MFC-Zustand beschädigt, wenn die MFC-Erweiterungs-DLL geladen und entladen wird.

Da die „MFCx0.dll“ bereits vollständig initialisiert ist, wenn DllMain aufgerufen wird, können Sie Speicher zuweisen und MFC-Funktionen in DllMain aufrufen (im Gegensatz zur 16-Bit-Version von MFC).

Erweiterungs-DLLs können das Multithreading durchführen, indem sie die Fälle DLL_THREAD_ATTACH und DLL_THREAD_DETACH in der DllMain-Funktion verarbeiten. Diese Fälle werden an DllMain übergeben, wenn Threads angefügt oder von der DLL getrennt werden. Durch Aufrufen von TlsAlloc beim Anfügen einer DLL, kann die DLL TLS-Indizes (Thread Local Storage, threadlokaler Speicher) für jeden Thread verwalten, der an die DLL angefügt ist.

Beachten Sie, dass die Headerdatei „Afxdllx.h“ besondere Definitionen für Strukturen enthält, die in MFC-Erweiterungs-DLLs verwendet werden, z. B. die Definition für AFX_EXTENSION_MODULE und CDynLinkLibrary. Sie sollten diese Headerdatei in Ihre MFC-Erweiterungs-DLL einschließen.

Hinweis

Es ist wichtig, dass Sie die _AFX_NO_XXX-Makros in pch.h (stdafx.h in Visual Studio 2017 und früher) weder definieren noch ihre Definition aufgeben. Diese Makros sind lediglich dafür vorgesehen, zu überprüfen, ob eine bestimmte Zielplattform das Feature unterstützt oder nicht. Sie können Ihr Programm so schreiben, dass es diese Makros überprüft (z. B. #ifndef _AFX_NO_OLE_SUPPORT), jedoch sollte Ihr Programm diese Makros nie definieren und ihre Definition nie aufgeben.

Eine Beispielinitialisierungsfunktion, die Multithreading verarbeitet, finden Sie unter Verwenden des threadlokalen Speichers in einer Dynamic Link Library im Windows SDK. Beachten Sie, dass das Beispiel eine Einstiegspunktfunktion namens LibMain enthält, jedoch sollten Sie diese Funktion DllMain nennen, damit sie im Zusammenhang mit den MFC- und C-Laufzeitbibliotheken funktioniert.

Siehe auch

Erstellen von C/C++-DLLs in Visual Studio
DllMain-Einstiegspunkt
Bewährte Methoden zur Verwendung von Dynamic Link Librarys