Podpora linkeru pro knihovny DLL s odloženým načtením
Linker MSVC podporuje zpožděné načítání knihoven DLL. Tato funkce vás zbavuje nutnosti používat funkce LoadLibrary
sady Windows SDK a GetProcAddress
implementovat zpožděné načítání knihovny DLL.
Bez zpožděného načtení je LoadLibrary
jediným způsobem, jak za běhu načíst knihovnu DLL, a GetProcAddress
; operační systém načte knihovnu DLL při načtení spustitelného souboru nebo knihovny DLL.
Při zpožděném načtení, když implicitně propojit knihovnu DLL, linker poskytuje možnosti zpoždění načtení knihovny DLL, dokud program nevolá funkci v této knihovně DLL.
Aplikace může zpozdit načtení knihovny DLL pomocí možnosti linkeru /DELAYLOAD
(delay load import) s pomocnou funkcí. (Microsoft poskytuje výchozí implementaci pomocné funkce.) Pomocná funkce načte knihovnu DLL na vyžádání za běhu voláním LoadLibrary
a GetProcAddress
za vás.
Zvažte zpoždění načtení knihovny DLL, pokud:
Program nemusí volat funkci v knihovně DLL.
Funkce v knihovně DLL nemusí být volána až do konce provádění programu.
Zpožděné načítání knihovny DLL lze zadat během sestavení projektu EXE nebo KNIHOVNY DLL. Projekt knihovny DLL, který zpožďuje načítání jednoho nebo více samotných knihoven DLL, by neměl volat vstupní bod načtený zpožděním v DllMain
souboru .
Určení knihoven DLL pro odložení načítání
Pomocí možnosti linkeru můžete určit, které knihovny DLL se mají zpozdit načtení /delayload:
dllname
. Pokud nemáte v úmyslu používat vlastní verzi pomocné funkce, musíte také propojit program s aplikacemi delayimp.lib
(pro desktopové aplikace) nebo dloadhelper.lib
(pro aplikace pro UPW).
Tady je jednoduchý příklad zpoždění načítání knihovny DLL:
// cl t.cpp user32.lib delayimp.lib /link /DELAYLOAD:user32.dll
#include <windows.h>
// uncomment these lines to remove .libs from command line
// #pragma comment(lib, "delayimp")
// #pragma comment(lib, "user32")
int main() {
// user32.dll will load at this point
MessageBox(NULL, "Hello", "Hello", MB_OK);
}
Sestavte verzi LADĚNÍ projektu. Krokujte kód pomocí ladicího programu a všimnete si, že user32.dll
je načten pouze při volání MessageBox
.
Explicitní uvolnění knihovny DLL načtené zpožděním
Možnost /delay:unload
linkeru umožňuje vašemu kódu explicitně uvolnit knihovnu DLL, která byla zpožděna načtena. Ve výchozím nastavení zůstanou importy načtené zpožděním v tabulce IAT (Import Address Table). Pokud však použijete /delay:unload
na příkazovém řádku linkeru, pomocná funkce podporuje explicitní uvolnění knihovny DLL voláním __FUnloadDelayLoadedDLL2
a resetuje IAT do původního formuláře. Nyní neplatné ukazatele se přepíší. IAT je pole ve ImgDelayDescr
struktuře, které obsahuje adresu kopie původního IAT, pokud existuje.
Příklad uvolnění knihovny DLL načtené zpožděním
Tento příklad ukazuje, jak explicitně uvolnit knihovnu DLL, MyDll.dll
která obsahuje funkci fnMyDll
:
// link with /link /DELAYLOAD:MyDLL.dll /DELAY:UNLOAD
#include <windows.h>
#include <delayimp.h>
#include "MyDll.h"
#include <stdio.h>
#pragma comment(lib, "delayimp")
#pragma comment(lib, "MyDll")
int main()
{
BOOL TestReturn;
// MyDLL.DLL will load at this point
fnMyDll();
//MyDLL.dll will unload at this point
TestReturn = __FUnloadDelayLoadedDLL2("MyDll.dll");
if (TestReturn)
printf_s("\nDLL was unloaded");
else
printf_s("\nDLL was not unloaded");
}
Důležité poznámky k uvolnění knihovny DLL načtené zpožděním:
Implementaci
__FUnloadDelayLoadedDLL2
funkce najdete v souborudelayhlp.cpp
, v adresáři MSVCinclude
. Další informace najdete v tématu Vysvětlení pomocné funkce načítání zpoždění.Parametr
name
__FUnloadDelayLoadedDLL2
funkce se musí přesně shodovat (včetně případu) toho, co knihovna importu obsahuje. (Tento řetězec je také v tabulce importu na obrázku.) Obsah knihovny importu můžete zobrazit pomocí příkazuDUMPBIN /DEPENDENTS
. Pokud dáváte přednost porovnávání řetězců nerozlišující velká a malá písmena, můžete aktualizovat__FUnloadDelayLoadedDLL2
tak, aby používala některou z funkcí řetězce CRT nerozlišující velká a malá písmena nebo volání rozhraní API systému Windows.
Vytvoření vazby importů načtených zpožděním
Výchozím chováním linkeru je vytvoření tabulky IAT (Bindable import address table) pro knihovnu DLL načtené zpožděním. Pokud je knihovna DLL vázána, pomocná funkce se pokusí použít vázané informace místo volání GetProcAddress
na každý odkazovaný import. Pokud se časové razítko nebo upřednostňovaná adresa neshoduje s datem v načtené knihovně DLL, pomocná funkce předpokládá, že tabulka vázaných adres importu je za aktuální. Pokračuje, jako kdyby IAT neexistuje.
Pokud nemáte v úmyslu svázat importy knihovny DLL načtené zpožděním, zadejte /delay:nobind
na příkazovém řádku linkeru. Linker nevygeneruje vázanou tabulku adres importu, která šetří místo v souboru obrázku.
Načtení všech importů pro knihovnu DLL se zpožděním
Funkce __HrLoadAllImportsForDll
, která je definována v delayhlp.cpp
, říká linkeru, aby načetl všechny importy z knihovny DLL, která byla zadána s možností linkeru /delayload
.
Při načítání všech importů najednou můžete centralizovat zpracování chyb na jednom místě. Můžete se vyhnout strukturovanému zpracování výjimek kolem všech skutečných volání importu. Zároveň se vyhne situaci, kdy vaše aplikace selže částečně procesem: Například pokud se pomocnému kódu nepodaří načíst import, po úspěšném načtení dalších.
Volání __HrLoadAllImportsForDll
nemění chování háků a zpracování chyb. Další informace naleznete v tématu Zpracování chyb a oznámení.
__HrLoadAllImportsForDll
porovnává s názvem uloženým v samotné knihovně DLL malá a velká písmena.
Tady je příklad, který používá __HrLoadAllImportsForDll
funkci volanou TryDelayLoadAllImports
k pokusu o načtení pojmenované knihovny DLL. Používá funkci , CheckDelayException
k určení chování výjimky.
int CheckDelayException(int exception_value)
{
if (exception_value == VcppException(ERROR_SEVERITY_ERROR, ERROR_MOD_NOT_FOUND) ||
exception_value == VcppException(ERROR_SEVERITY_ERROR, ERROR_PROC_NOT_FOUND))
{
// This example just executes the handler.
return EXCEPTION_EXECUTE_HANDLER;
}
// Don't attempt to handle other errors
return EXCEPTION_CONTINUE_SEARCH;
}
bool TryDelayLoadAllImports(LPCSTR szDll)
{
__try
{
HRESULT hr = __HrLoadAllImportsForDll(szDll);
if (FAILED(hr))
{
// printf_s("Failed to delay load functions from %s\n", szDll);
return false;
}
}
__except (CheckDelayException(GetExceptionCode()))
{
// printf_s("Delay load exception for %s\n", szDll);
return false;
}
// printf_s("Delay load completed for %s\n", szDll);
return true;
}
Výsledek TryDelayLoadAllImports
můžete použít k určení, jestli voláte funkce importu, nebo ne.
Zpracování chyb a oznámení
Pokud váš program používá knihovny DLL s odloženým načtením, musí zpracovávat chyby robustním způsobem. Selhání, ke kterým dojde, když program běží, způsobí neošetřené výjimky. Další informace o zpracování chyb a oznámení o zpoždění načítání knihovny DLL naleznete v tématu Zpracování chyb a oznámení.
Importy se zpožděním načtení výpisu paměti
Importy načtené zpožděním lze vyřadit pomocí .DUMPBIN /IMPORTS
Tyto importy se zobrazují s mírně odlišnými informacemi než standardní importy. Jsou odděleny do vlastního oddílu /imports
seznamu a jsou explicitně označené jako importy načtené zpožděním. Pokud jsou na obrázku uvedeny informace o uvolnění, je uvedeno. Pokud jsou k dispozici informace o vazbě, je uvedeno časové razítko a datum cílové knihovny DLL spolu s vázané adresy importu.
Omezení knihoven DLL se zpožděním
Existuje několik omezení pro zpoždění načítání importů knihoven DLL.
Import dat není možné podporovat. Alternativním řešením je explicitně zpracovat import dat sami pomocí
LoadLibrary
(nebo pomocíGetModuleHandle
, jakmile víte, že pomocník pro odložené načtení načetl knihovnu DLL) aGetProcAddress
.Zpoždění načítání
Kernel32.dll
se nepodporuje. Tato knihovna DLL musí být načtena, aby pomocné rutiny pro odložené načítání fungovaly.Vazba přesměrovaných vstupních bodů se nepodporuje.
Proces může mít jiné chování, pokud je knihovna DLL načtena zpožděním místo načtení při spuštění. Je vidět, zda existují inicializace pro jednotlivé procesy, ke kterým dochází v vstupním bodě knihovny DLL s odloženým načtením. Mezi další případy patří statické tls (místní úložiště vlákna), deklarované pomocí
__declspec(thread)
, který není zpracován při načtení knihovny DLL prostřednictvímLoadLibrary
. Dynamické tls, usingTlsAlloc
,TlsFree
TlsGetValue
, aTlsSetValue
, je stále k dispozici pro použití v statických nebo pozdržované knihovny DLL.Znovu inicializovat statické globální ukazatele funkce na importované funkce po prvním volání každé funkce. To je povinné, protože první použití ukazatele funkce odkazuje na thunk, nikoli načtenou funkci.
V současné době neexistuje způsob, jak zpozdit načítání pouze konkrétních procedur z knihovny DLL při použití normálního mechanismu importu.
Vlastní konvence volání (například použití kódů podmínek v architektuře x86) se nepodporují. Registry s plovoucí desetinou čárkou se také neukládají na žádné platformě. Dbejte na to, jestli vlastní pomocné rutiny nebo rutiny háku používají typy s plovoucí desetinou čárkou: Rutiny musí ukládat a obnovovat úplný stav s plovoucí desetinou čárkou na počítačích, které používají registrovat konvence volání s parametry s plovoucí desetinou čárkou. Při načítání knihovny DLL CRT buďte opatrní, zejména pokud voláte funkce CRT, které u zásobníku procesoru číselných dat (NDP) ve funkci nápovědy přebírají parametry s plovoucí desetinou čárkou.
Vysvětlení pomocné funkce pro odložené načítání
Pomocná funkce pro zpožděné načítání podporované linkerem je to, co ve skutečnosti načte knihovnu DLL za běhu. Pomocnou funkci můžete upravit tak, aby přizpůsobila její chování. Místo použití dodané pomocné funkce v delayimp.lib
, napište vlastní funkci a propojte ji s programem. Jedna pomocná funkce obsluhuje všechny knihovny DLL načtené zpožděním. Další informace najdete v tématu Vysvětlení pomocné funkce pro zpoždění a vývoj vlastní pomocné funkce.
Viz také
Vytváření knihoven DLL jazyka C/C++ v sadě Visual Studio
Referenční zdroje k linkeru MSVC