Knihovny DLL a chování běhové knihovny v jazyce Visual C++

Při vytváření knihovny DLL (Dynamic-link Library) pomocí sady Visual Studio ve výchozím nastavení obsahuje linker knihovnu runtime visual C++ (VCRuntime). VCRuntime obsahuje kód potřebný k inicializaci a ukončení spustitelného souboru C/C++. Při propojení s knihovnou DLL poskytuje kód VCRuntime interní funkci vstupního bodu knihovny DLL, _DllMainCRTStartup která zpracovává zprávy operačního systému Windows do knihovny DLL pro připojení nebo odpojení od procesu nebo vlákna. Funkce _DllMainCRTStartup provádí základní úlohy, jako je nastavení zabezpečení vyrovnávací paměti zásobníku, inicializace a ukončení knihovny C runtime (CRT) a volání konstruktorů a destruktorů pro statické a globální objekty. _DllMainCRTStartup také volá funkce háku pro jiné knihovny, jako je WinRT, MFC a ATL, k provedení vlastní inicializace a ukončení. Bez této inicializace by crt a další knihovny a vaše statické proměnné zůstaly v neinicializovaném stavu. Stejná interní inicializace A ukončovací rutiny VCRuntime se nazývají, zda vaše knihovna DLL používá staticky propojenou CRT nebo dynamicky propojenou knihovnu CRT DLL.

Výchozí vstupní bod knihovny DLL _DllMainCRTStartup

Ve Windows můžou všechny knihovny DLL obsahovat volitelnou vstupní funkci, která se obvykle nazývá DllMain, která se volá pro inicializaci i ukončení. Díky tomu můžete podle potřeby přidělit nebo uvolnit další prostředky. Systém Windows volá funkci vstupního bodu ve čtyřech situacích: připojení procesu, odpojení procesu, připojení vlákna a odpojení vlákna. Při načtení knihovny DLL do adresního prostoru procesu, a to buď při načtení aplikace, která ji používá, nebo když aplikace požaduje knihovnu DLL za běhu, operační systém vytvoří samostatnou kopii dat knihovny DLL. Tomu se říká připojení procesu. Připojení vlákna nastane, když proces, který je knihovna DLL načtena do vytvoření nového vlákna. Při ukončení vlákna dojde k odpojení vlákna a proces odpojení je v případě, že knihovna DLL již není vyžadována a je vydána aplikací. Operační systém vytvoří samostatné volání vstupního bodu knihovny DLL pro každou z těchto událostí a předává argument důvodu pro každý typ události. Operační systém například odešle DLL_PROCESS_ATTACH jako argument důvodu připojení procesu signálu.

Knihovna VCRuntime poskytuje funkci vstupního bodu volanou _DllMainCRTStartup pro zpracování výchozích operací inicializace a ukončení. Při připojení _DllMainCRTStartup procesu funkce nastaví kontroly zabezpečení vyrovnávací paměti, inicializuje CRT a další knihovny, inicializuje informace o typu za běhu, inicializuje a volá konstruktory pro statická a nemístně uložená data, inicializuje místní úložiště vláken, zvýší interní statický čítač pro každé připojení a potom zavolá uživatelem nebo knihovnou zadanou DllMainknihovnou . Při odpojení procesu funkce prochází těmito kroky obráceně. DllMainVolá , dekrementuje interní čítač, volá destruktory, volá funkce ukončení CRT a registrované atexit funkce a upozorní všechny ostatní knihovny ukončení. Když čítač přílohy přejde na nulu, vrátí FALSE funkce indikaci systému Windows, že knihovnu DLL lze uvolnit. Funkce _DllMainCRTStartup se také volá během připojení vlákna a odpojení vlákna. V těchto případech kód VCRuntime sám neprovádí žádné další inicializace nebo ukončení a pouze volá, DllMain aby předala zprávu. Pokud DllMain se vrátí FALSE z připojení procesu, signalizují selhání, _DllMainCRTStartup volání DllMain znovu a předá DLL_PROCESS_DETACH se jako argument důvodu , pak projde zbytek procesu ukončení.

Při vytváření knihoven DLL v sadě Visual Studio se výchozí vstupní bod _DllMainCRTStartup zadaný VCRuntime propojil automaticky. Funkci vstupního bodu pro knihovnu DLL není nutné zadávat pomocí možnosti linkeru /ENTRY (symbol vstupního bodu).

Poznámka:

I když je možné zadat jinou funkci vstupního bodu pro knihovnu DLL pomocí možnosti /ENTRY: linker, nedoporučujeme ji, protože funkce vstupního bodu by musela duplikovat vše, co _DllMainCRTStartup dělá, ve stejném pořadí. VCRuntime poskytuje funkce, které umožňují duplikovat jeho chování. Můžete například volat __security_init_cookie okamžitě při připojení procesu pro podporu kontroly vyrovnávací paměti /GS (kontrola zabezpečení vyrovnávací paměti). Funkci můžete volat _CRT_INIT a předat stejné parametry jako funkce vstupního bodu, aby se zbývající funkce inicializace nebo ukončení knihovny DLL provedly.

Inicializace knihovny DLL

Knihovna DLL může mít inicializační kód, který se musí spustit při načtení knihovny DLL. Aby bylo možné provádět vlastní inicializační a ukončovací funkce knihovny DLL, volá funkci, _DllMainCRTStartup DllMain kterou můžete poskytnout. Váš DllMain podpis musí mít požadovaný podpis pro vstupní bod knihovny DLL. Výchozí volání DllMain funkce _DllMainCRTStartup vstupního bodu pomocí stejných parametrů předaných systémem Windows. Pokud funkci nezadáte DllMain , Visual Studio vám ji ve výchozím nastavení poskytne a propojí ji tak, aby _DllMainCRTStartup vždy mělo co volat. To znamená, že pokud nepotřebujete inicializovat knihovnu DLL, není při sestavování knihovny DLL nic zvláštního.

Toto je podpis použitý pro 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

Některé knihovny zabalí DllMain funkci za vás. Například v běžné knihovně MFC DLL implementujte CWinApp objekty InitInstance a ExitInstance členské funkce k provedení inicializace a ukončení požadované knihovnou DLL. Další podrobnosti najdete v části Inicializace běžných knihoven MFC DLL.

Upozorňující

Existují významná omezení toho, co můžete bezpečně dělat v vstupním bodě knihovny DLL. Další informace o konkrétních rozhraních API systému Windows, která nejsou bezpečná pro volání, naleznete v DllMaintématu Obecné osvědčené postupy. Pokud potřebujete cokoli, ale nejjednodušší inicializace, udělejte to ve funkci inicializace knihovny DLL. Po spuštění a před voláním jiných funkcí v knihovně DLL můžete vyžadovat, aby aplikace volaly inicializační funkci DllMain .

Inicializace běžných knihoven DLL (mimo MFC)

Chcete-li provést vlastní inicializaci v běžných knihovnách DLL (jiné než MFC), které používají vstupní bod zadaný VCRuntime _DllMainCRTStartup , musí zdrojový kód knihovny DLL obsahovat funkci s názvem DllMain. Následující kód představuje základní kostru, která ukazuje, jak může vypadat definice 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.
}

Poznámka:

Starší dokumentace k sadě Windows SDK uvádí, že skutečný název funkce vstupního bodu knihovny DLL musí být zadán na příkazovém řádku linkeru s možností /ENTRY. V sadě Visual Studio nemusíte používat možnost /ENTRY, pokud je DllMainnázev funkce vstupního bodu . Ve skutečnosti, pokud použijete možnost /ENTRY a pojmenujete funkci vstupního bodu něco jiného než DllMain, CRT se neinicializuje správně, pokud vaše vstupní bod funkce neprovede stejná inicializační volání, která _DllMainCRTStartup provádí.

Inicializace běžných knihoven MFC DLL

Vzhledem k tomu, že běžné knihovny MFC DLL mají CWinApp objekt, měly by provádět úlohy inicializace a ukončení ve stejném umístění jako aplikace MFC: v InitInstance a ExitInstance členské funkce knihovny DLL CWinApp-odvozené třídy. Vzhledem k tomu, že MFC poskytuje DllMain funkci, která je volána _DllMainCRTStartup pro DLL_PROCESS_ATTACH a DLL_PROCESS_DETACH, neměli byste psát vlastní DllMain funkci. Volání funkce InitInstance poskytované knihovnou DllMain MFC při načtení knihovny DLL a volání ExitInstance před uvolněním knihovny DLL.

Běžná knihovna MFC DLL může sledovat více vláken voláním TlsAlloc a TlsGetValue ve své InitInstance funkci. Tyto funkce umožňují knihovně DLL sledovat data specifická pro vlákna.

V běžné knihovně MFC DLL, která dynamicky odkazuje na knihovnu MFC, pokud používáte podporu knihovny MFC OLE, databáze MFC (nebo rozhraní DAO) nebo podpory soketů MFC, v uvedeném pořadí ladicí rozšiřující knihovny MFCOverze D.dll, D.dll verzeMFCDa D.dll verzeMFCN(kde verze je číslo verze), jsou automaticky propojeny. Pro každou z těchto knihoven DLL, které používáte v běžných knihovnách MFC CWinApp::InitInstanceDLL, je nutné volat jednu z následujících předdefinovaných inicializačních funkcí.

Typ podpory MFC Inicializační funkce pro volání
MFC OLE (D.dll verzeMFCO) AfxOleInitModule
DATABÁZE MFC (D.dll verzeMFCD) AfxDbInitModule
ROZHRANÍ MFC Sokety (D.dll verzeMFCN) AfxNetInitModule

Inicializace knihoven DLL rozšíření MFC

Vzhledem k tomu, že rozšiřující knihovny DLL knihovny MFC nemají odvozený CWinAppobjekt (stejně jako běžné knihovny MFC DLL), měli byste přidat inicializační a ukončovací kód do DllMain funkce, kterou generuje Průvodce knihovnou MFC DLL.

Průvodce poskytuje následující kód pro knihovny DLL rozšíření MFC. V kódu PROJNAME je zástupný symbol pro název projektu.

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

Vytvoření nového CDynLinkLibrary objektu během inicializace umožňuje knihovně DLL rozšíření MFC exportovat CRuntimeClass objekty nebo prostředky do klientské aplikace.

Pokud budete používat knihovnu DLL rozšíření MFC z jedné nebo více běžných knihoven MFC DLL, musíte exportovat inicializační funkci, která vytvoří CDynLinkLibrary objekt. Tato funkce musí být volána z každé z běžných knihoven MFC DLL, které používají rozšiřující knihovnu DLL knihovny MFC. Vhodným místem pro volání této inicializační funkce je členská InitInstance funkce běžného objektu odvozeného knihovny MFC DLL CWinApppřed použitím některé z exportovaných tříd nebo funkcí knihovny DLL rozšíření MFC.

DllMain V tom, že průvodce MFC DLL generuje volání AfxInitExtensionModule zachytává třídy runtime modulu (CRuntimeClassstruktury) a jeho objektové továrny (COleObjectFactoryobjekty) pro použití při vytváření objektuCDynLinkLibrary. Měli byste zkontrolovat návratovou AfxInitExtensionModulehodnotu ; pokud je vrácena nulová hodnota z AfxInitExtensionModulefunkce , vrátit nulu z funkce DllMain .

Pokud bude vaše knihovna DLL rozšíření MFC explicitně propojena se spustitelným souborem (což znamená volání AfxLoadLibrary spustitelného souboru pro propojení s knihovnou DLL), měli byste přidat volání AfxTermExtensionModule .DLL_PROCESS_DETACH Tato funkce umožňuje knihovně MFC vyčistit knihovnu DLL rozšíření MFC, když se každý proces odpojí od knihovny DLL rozšíření MFC (což se stane, když se proces ukončí nebo když je knihovna DLL uvolněna v důsledku AfxFreeLibrary volání). Pokud se vaše knihovna DLL rozšíření MFC implicitně propojí s aplikací, AfxTermExtensionModule volání není nutné.

Aplikace, které explicitně propojují knihovny DLL rozšíření MFC, musí při uvolnění knihovny DLL volat AfxTermExtensionModule . Měly by také používat AfxLoadLibrary funkce Win32 a AfxFreeLibrary (místo funkcí LoadLibrary Win32) FreeLibrarypokud aplikace používá více vláken. Použití AfxLoadLibrary a AfxFreeLibrary zajištění spuštění a vypnutí kódu, který se spustí při načtení a uvolnění knihovny DLL rozšíření MFC není poškozen globální stav MFC.

Vzhledem k tomu, že MFCx0.dll je plně inicializován v době volání, DllMain můžete přidělit paměť a volat funkce MFC v rámci DllMain (na rozdíl od 16bitové verze MFC).

Rozšiřující knihovny DLL se můžou postarat o vícevláknové zpracování DLL_THREAD_ATTACH DLL_THREAD_DETACH a případy ve DllMain funkci. Tyto případy se předávají DllMain , když vlákna připojí a odpojí od knihovny DLL. Volání tlsAlloc při připojení knihovny DLL umožňuje knihovně DLL udržovat indexy místního úložiště vláken (TLS) pro každé vlákno připojené k knihovně DLL.

Všimněte si, že hlavičkový soubor Afxdllx.h obsahuje speciální definice pro struktury používané v rozšiřujících knihovnách MFC DLL, jako je definice a AFX_EXTENSION_MODULE CDynLinkLibrary. Tento hlavičkový soubor byste měli zahrnout do knihovny DLL s příponou MFC.

Poznámka:

Je důležité, abyste nedefinovat ani nedefinovat žádná makra _AFX_NO_XXX v souboru pch.h (stdafx.h v sadě Visual Studio 2017 a starší). Tato makra existují pouze pro účely kontroly, jestli konkrétní cílová platforma tuto funkci podporuje, nebo ne. Program můžete napsat a zkontrolovat tato makra (například #ifndef _AFX_NO_OLE_SUPPORT), ale program by neměl nikdy definovat nebo nedefinovat tato makra.

Ukázková inicializační funkce, která zpracovává vícevláknové zpracování, je součástí použití místního úložiště vláken v knihovně Dynamic-Link v sadě Windows SDK. Všimněte si, že ukázka obsahuje funkci vstupního bodu volanou LibMain, ale tuto funkci DllMain byste měli pojmenovat tak, aby fungovala s knihovnami MFC a jazyka C za běhu.

Viz také

Vytváření knihoven DLL jazyka C/C++ v sadě Visual Studio
Vstupní bod DllMain
Osvědčené postupy pro dynamickou knihovnu