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 DllMain
knihovnou . Při odpojení procesu funkce prochází těmito kroky obráceně. DllMain
Volá , 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 DllMain
té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 DllMain
ná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::InitInstance
DLL, 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ý CWinApp
objekt (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 CWinApp
př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 (CRuntimeClass
struktury) a jeho objektové továrny (COleObjectFactory
objekty) pro použití při vytváření objektuCDynLinkLibrary
. Měli byste zkontrolovat návratovou AfxInitExtensionModule
hodnotu ; pokud je vrácena nulová hodnota z AfxInitExtensionModule
funkce , 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) FreeLibrary
pokud 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