Knihovny DLL a chování běhové knihovny v jazyce Visual C++
Když sestavíte dynamickou knihovnu (DLL) pomocí Visual Studio, linker ve výchozím nastavení zahrnuje Visual C++ modulu run-time (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 s názvem , která zpracovává zprávy operačního systému Windows knihovně DLL pro připojení nebo odpojení od procesu _DllMainCRTStartup nebo vlákna. Funkce provádí základní úlohy, jako je nastavení zabezpečení vyrovnávací paměti zásobníku, inicializace a ukončení knihovny CRT (Run-Time Library) jazyka C a volání konstruktorů a destruktorů pro statické a _DllMainCRTStartup globální objekty. _DllMainCRTStartup také volá funkce volání pro další knihovny, jako jsou WinRT, MFC a ATL, aby bylo možné provést vlastní inicializaci a ukončení. Bez této inicializace by crt a další knihovny a statické proměnné zůstaly v neinicializovaném stavu. Stejné rutiny vnitřní inicializace a ukončení VCRuntime jsou volány bez ohledu na to, jestli vaše knihovna DLL používá staticky propojenou knihovnu CRT nebo dynamicky propojenou knihovnu CRT DLL.
Výchozí vstupní bod knihovny DLL _DllMainCRTStartup
V Windows mohou všechny knihovny DLL obsahovat volitelnou funkci vstupního bodu, která se obvykle nazývá , která je volána pro inicializaci i DllMain ukončení. To vám dává příležitost přidělit nebo uvolnit další prostředky podle potřeby. Windows volá funkci vstupního bodu ve čtyřech situacích: připojení procesu, odpojení procesu, připojení vlákna a odpojení vlákna. Když je knihovna DLL načtena do adresního prostoru procesu, buď při načtení aplikace, která ji používá, nebo když aplikace požaduje knihovnu DLL za běhu, vytvoří operační systém samostatnou kopii dat knihovny DLL. Tomu se říká připojení procesu. K připojení vlákna dochází v případě, že proces, do které je knihovna DLL načtena, vytvoří nové vlákno. K odpojení vlákna dochází při ukončení vlákna a odpojení procesu je v případě, že knihovna DLL již není požadována a je uvolněna aplikací. Operační systém provádí 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 jako DLL_PROCESS_ATTACH argument DLL_PROCESS_ATTACH signálu připojení procesu.
Knihovna VCRuntime poskytuje funkci vstupního bodu s názvem pro zpracování _DllMainCRTStartup výchozích operací inicializace a ukončení. Při připojení procesu funkce nastaví kontroly zabezpečení vyrovnávací paměti, inicializuje CRT a další knihovny, inicializuje informace o typu běhu, inicializuje a volá konstruktory pro statická i nemístně daná data, inicializuje místní úložiště vláken, zvýší interní statický čítač pro každé připojení a potom zavolá uživatelský objekt nebo _DllMainCRTStartupDllMain knihovnu. Při odpojení procesu funkce prochází těmito kroky v opačném pořadí. Volá DllMain , dekrementuje interní čítač, volá destruktory, volá funkce ukončení CRT a registrované funkce a upozorní všechny ostatní atexit knihovny ukončení. Když čítač přílohy přejde na nulu, funkce se vrátí, aby FALSE Windows, že knihovna DLL může být uvolněna. Funkce _DllMainCRTStartup je také volána během připojení vlákna a odpojení vlákna. V těchto případech kód VCRuntime sám o sobě neprovolá žádnou další inicializaci nebo ukončení a pouze volá metodu , která DllMain zprávu předá. Pokud DllMain se vrátí z připojení procesu, signalizováním selhání, znovu zavolá a předá se jako argument důvodu, pak projde zbytkem FALSE_DllMainCRTStartup procesu DllMainDLL_PROCESS_DETACH ukončení. DllMain
Při sestavování knihoven DLL v Visual Studio, výchozí vstupní bod dodaný _DllMainCRTStartup VCRuntime je propojen v automaticky. Funkci vstupního bodu pro knihovnu DLL není nutné zazadat pomocí možnosti linkeru /ENTRY (symbol vstupního bodu).
Poznámka
I když je možné zadat další funkci vstupního bodu pro knihovnu DLL pomocí možnosti linkeru /ENTRY: , nedoporučujeme to, protože funkce vstupního bodu by muset duplikovat všechno, co dělá, ve _DllMainCRTStartup stejném pořadí. VCRuntime poskytuje funkce, které umožňují duplikovat jeho chování. Můžete například okamžitě volat volání __security_init_cookie připojení procesu pro podporu možnosti kontroly vyrovnávací paměti /GS (kontrola zabezpečení vyrovnávací paměti). Funkci můžete volat a předáte stejné parametry jako funkce vstupního bodu, abyste mohli provést zbytek funkcí inicializace nebo ukončení _CRT_INIT knihovny DLL.
Inicializace knihovny DLL
Vaše knihovna DLL může mít inicializační kód, který se musí spustit při načtení knihovny DLL. Abyste mohli provést vlastní funkce inicializace a ukončení knihovny DLL, volá funkci s názvem , _DllMainCRTStartupDllMain kterou můžete zadat. Musí DllMain mít podpis vyžadované pro vstupní bod knihovny DLL. Výchozí funkce vstupního bodu _DllMainCRTStartup volá DllMain pomocí stejných parametrů, které předá Windows. Pokud ve výchozím nastavení nezadáte funkci, Visual Studio ji za vás a prodá ji tak, aby měla vždy co DllMain_DllMainCRTStartup volat. To znamená, že pokud knihovnu DLL nepotřebujete inicializovat, není při sestavování knihovny DLL potřeba nic zvláštního.
Toto je podpis, který se používá 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 za vás DllMain funkci zabalí. Například v běžné knihovně MFC DLL implementujte členské funkce objektu a k provedení inicializace a CWinApp ukončení vyžadované InitInstanceExitInstance knihovnou DLL. Další podrobnosti najdete v části Inicializace běžných knihoven MFC DLL.
Upozornění
Existují významná omezení toho, co můžete bezpečně dělat ve vstupním bodě knihovny DLL. V tématu Obecné osvědčené postupy najdete Windows rozhraní API, která nejsou bezpečná pro volání v . Pokud potřebujete cokoli kromě nejjednodušší inicializace, proveďte to ve funkci inicializace pro knihovnu DLL. Můžete vyžadovat, aby aplikace volaly inicializační funkci po spuštění a před voláním všech dalších DllMain funkcí v knihovně DLL.
Inicializace běžných knihoven DLL (mimo MFC)
Pokud chcete provést vlastní inicializaci v běžných knihovnách DLL (mimo MFC), které používají vstupní bod zadaný VCRuntime, musí zdrojový kód knihovny DLL obsahovat funkci s _DllMainCRTStartup názvem DllMain . Následující kód představuje základní kostru, která ukazuje, jak může DllMain definice vypadat:
#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ší Windows SDK říká, že skutečný název funkce vstupního bodu knihovny DLL musí být zadaný na příkazovém řádku linkeru s možností /ENTRY. V Visual Studio není nutné použít možnost /ENTRY, pokud je název funkce vstupního bodu DllMain . Ve skutečnosti platí, že pokud použijete možnost /ENTRY a pojmete funkci vstupního bodu něco jiného než , CRT se neinicializoval správně, pokud funkce vstupního bodu neprovolá stejná DllMain volání inicializace, která _DllMainCRTStartup provádí.
Inicializace běžných knihoven MFC DLL
Vzhledem k tomu, že běžné knihovny MFC DLL mají objekt , měly by provádět své úlohy inicializace a ukončení ve stejném umístění jako aplikace MFC: v členských funkcích a třídy odvozené od knihovny CWinAppInitInstanceExitInstanceCWinApp DLL. Vzhledem k tomu, že knihovna MFC poskytuje funkci, která je volána funkcí pro a , neměli DllMain_DllMainCRTStartup byste psát vlastní DLL_PROCESS_ATTACHDLL_PROCESS_DETACHDllMain funkci. Funkce poskytnutá knihovnou MFC volá při načtení knihovny DLL a DllMainInitInstance volá před ExitInstance 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é funkci. Tyto funkce umožňují knihovně DLL sledovat data specifická pro vlákno.
V běžné knihovně MFC DLL, která se dynamicky propojí s knihovnou MFC, pokud používáte rozhraní MFC OLE, databázi MFC (nebo rozhraní DAO) nebo podporu soketů MFC, jsou knihovny MFC DLL verzeMFCOD.dll, mfcd verzeD.dll a verzeMFCND.dll (kde verze je číslo verze) propojeny automaticky v nástroji . Pro každou z těchto knihoven DLL, které používáte v běžné knihovně MFC DLL, je nutné volat jednu z následujících předdefinovaných inicializačních CWinApp::InitInstance funkcí.
| Typ podpory MFC | Inicializační funkce pro volání |
|---|---|
| MFC OLE (MFCOverzeD.dll) | AfxOleInitModule |
| Databáze MFC (MFCDverzeD.dll) | AfxDbInitModule |
| Mfc – sokety (mfcnverzeD.dll) | AfxNetInitModule |
Inicializace rozšiřujících knihoven DLL knihovny MFC
Vzhledem k tomu, že rozšiřující knihovny MFC DLL nemají objekt odvozený (stejně jako běžné knihovny MFC DLL), měli byste do funkce, kterou průvodce knihovnou MFC DLL generuje, přidat inicializační a ukončovací CWinAppDllMain kód.
Průvodce poskytuje následující kód pro rozšiřující knihovny MFC DLL. 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 objektu během inicializace umožňuje knihovně DLL rozšíření MFC exportovat objekty nebo CDynLinkLibrary prostředky do klientské CRuntimeClass aplikace.
Pokud budete používat rozšiřující knihovnu DLL knihovny 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 jednotlivých běžných knihoven MFC DLL, které používají rozšiřující knihovnu MFC DLL. Vhodné místo pro volání této inicializační funkce je před použitím exportovaných tříd nebo funkcí exportovaných tříd nebo funkcí knihovny MFC v rozšiřující knihovně DLL členem členské funkce objektu odvozeného od knihovny InitInstanceCWinApp MFC DLL.
V průvodci knihovnou MFC DLL generuje volání zachytávání běhových tříd modulu ( struktur) a také objektových továren ( objektů) pro použití při vytváření DllMainAfxInitExtensionModuleCRuntimeClassCOleObjectFactoryCDynLinkLibrary objektu. Měli byste zkontrolovat návratovou hodnotu . Pokud se z vrátí nulová hodnota AfxInitExtensionModuleAfxInitExtensionModule , vrátí funkce DllMain nulu.
Pokud vaše knihovna DLL rozšíření KNIHOVNY MFC bude explicitně propojena se spustitelným souborem (to znamená, že spustitelný soubor volá propojení s knihovnou DLL), měli byste přidat AfxLoadLibrary volání AfxTermExtensionModule do souboru DLL_PROCESS_DETACH . Tato funkce umožňuje knihovně MFC vyčistit rozšiřující knihovnu DLL knihovny MFC, když se každý proces odpojí od rozšiřující knihovny MFC DLL (k tomu dochází při ukončení procesu nebo uvolnění knihovny DLL v důsledku AfxFreeLibrary volání). Pokud bude vaše rozšiřující knihovna DLL knihovny MFC implicitně propojena s aplikací, není volání AfxTermExtensionModule metody nezbytné.
Aplikace, které explicitně odkazují na rozšiřující knihovny DLL KNIHOVNY MFC, musí při AfxTermExtensionModule volných knihovnách DLL volat . Měli by také použít a (místo funkcí Win32 a ), pokud AfxLoadLibraryAfxFreeLibrary aplikace používá více LoadLibraryFreeLibrary vláken. Pomocí AfxLoadLibrary a zajistíte, že kód pro spuštění a vypnutí, který se spustí při načtení a uvolnění knihovny MFC rozšiřující knihovny DLL, nepoškodí AfxFreeLibrary globální stav knihovny MFC.
Vzhledem k MFCx0.dll je funkce plně inicializována v době je volána, můžete přidělit paměť a volat funkce MFC v rámci (na rozdíl od DllMainDllMain 16bitové verze knihovny MFC).
Rozšiřující knihovny DLL se starají o multithreading zpracováním případů a DLL_THREAD_ATTACHDLL_THREAD_DETACH ve funkci DllMain . Tyto případy jsou předány DllMain do, když se vlákna připojí a odpojí od knihovny DLL. Volání tlsAlloc při připojování 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 DLL knihovny MFC, jako je například definice pro a AFX_EXTENSION_MODULECDynLinkLibrary . Tento soubor hlaviček byste měli zahrnout do rozšiřující knihovny DLL knihovny MFC.
Poznámka
Je důležité, abyste v souboru _AFX_NO_XXX_AFX_NO_XXX (stdafx.h v Visual Studio 2017 a dřívějších verzích nedefinujte ani nedefinujte žádná makra). Tato makra existují pouze za účelem kontroly, zda konkrétní cílová platforma tuto funkci podporuje nebo ne. Můžete napsat program, který tato makra zkontroluje (například ), ale program by nikdy neměl tato makra definovat ani #ifndef _AFX_NO_OLE_SUPPORT nedefinovat.
Ukázková inicializační funkce, která zpracovává multithreading, je součástí třídy Using Thread Local Storage v knihovně Dynamic-Link v sadě Windows SDK. Všimněte si, že ukázka obsahuje funkci vstupního bodu s názvem , ale měli byste ji pojmenovat tak, aby fungovala s knihovnami běhových knihoven MFC a LibMainDllMain C.
Viz také
Vytváření knihoven DLL jazyka C/C++ v Visual Studio
Vstupní bod DllMain
Osvědčené postupy pro dynamickou knihovnu