Propojení spustitelného souboru s knihovnou DLL
Spustitelný soubor odkazuje na (nebo načte) knihovnu DLL jedním ze dvou způsobů:
Implicitní propojování, kde operační systém načte knihovnu DLL ve stejnou dobu jako spustitelný soubor, který ji používá. Spustitelný soubor klienta volá exportované funkce knihovny DLL stejným způsobem, jako kdyby byly funkce staticky propojeny a obsaženy ve spustitelném souboru. Implicitní propojení se někdy označuje jako statické zatížení nebo dynamické propojení za běhu načítání.
Explicitní propojení, kde operační systém načte knihovnu DLL na vyžádání za běhu. Spustitelný soubor, který používá knihovnu DLL explicitním propojením, musí knihovnu DLL explicitně načíst a uvolnit. Musí také nastavit ukazatel na funkci pro přístup ke každé funkci, která používá z knihovny DLL. Na rozdíl od volání funkcí ve staticky propojené knihovně nebo implicitně propojené knihovně DLL musí spustitelný soubor klienta volat exportované funkce v explicitně propojené knihovně DLL prostřednictvím ukazatelů na funkce. Explicitní propojení se někdy označuje jako dynamické načtení nebo dynamické propojení za běhu.
Spustitelný soubor může pro propojení se stejnou knihovnou DLL použít některou z metod propojení. Tyto metody se navíc vzájemně nevyluují. Jeden spustitelný soubor může implicitně odkazovat na knihovnu DLL a jiný se k ní může připojit explicitně.
Určení metody propojení, která se má použít
To, jestli se má použít implicitní propojení nebo explicitní propojení, je rozhodnutí o architektuře, které musíte udělat pro svou aplikaci. Každá metoda má své výhody a nevýhody.
Implicitní propojování
K implicitnímu propojení dochází v případě, že kód aplikace volá exportované funkce knihovny DLL. Pokud je zdrojový kód volajícího spustitelného souboru zkompilován nebo sestaven, volání funkce knihovny DLL vygeneruje odkaz na externí funkci v kódu objektu. Pokud chcete tento externí odkaz vyřešit, musí aplikace propojit knihovnu importu (soubor .lib) poskytnutou tvůrcem knihovny DLL.
Knihovna importu obsahuje pouze kód pro načtení knihovny DLL a implementaci volání funkcí v knihovně DLL. Vyhledání externí funkce v knihovně importu informuje linker, že kód této funkce je v knihovně DLL. Pokud chcete vyřešit externí odkazy na knihovny DLL, linker jednoduše přidá informace do spustitelného souboru, který systému říká, kde má najít kód knihovny DLL při spuštění procesu.
Když systém spustí program, který obsahuje dynamicky propojené odkazy, použije informace ve spustitelném souboru programu k vyhledání požadovaných knihoven DLL. Pokud knihovna DLL nemůže najít, systém proces ukončí a zobrazí dialogové okno s oznámením o chybě. V opačném případě systém mapuje moduly knihovny DLL na adresní prostor procesu.
Pokud má kterýkoli z knihoven DLL funkci vstupního bodu pro inicializaci a ukončovací kód, jako je DllMain , operační systém funkci volá. Jeden z parametrů předaný funkci vstupního bodu určuje kód, který označuje, že se knihovna DLL připojuje k procesu. Pokud funkce vstupního bodu nevrátí hodnotu TRUE, systém proces ukončí a o hlásí chybu.
Nakonec systém upraví spustitelný kód procesu tak, aby poskytoval počáteční adresy pro funkce knihovny DLL.
Stejně jako zbytek kódu programu namapuje zavaděč kód knihovny DLL do adresního prostoru procesu při spuštění procesu. Operační systém ho načte do paměti pouze v případě potřeby. V důsledku toho atributy kódu a používané soubory .def k řízení načítání v předchozích verzích PRELOADLOADONCALL Windows již nemají význam.
Explicitní propojování
Většina aplikací používá implicitní propojení, protože se jedná o nejjednodušší metodu propojení, kterou je dobré použít. Existují však doby, kdy je explicitní propojení nezbytné. Tady jsou některé běžné důvody použití explicitního propojení:
Aplikace nebude znát název knihovny DLL, kterou načte, dokud se neza běhu. Aplikace může například získat název knihovny DLL a exportované funkce z konfiguračního souboru při spuštění.
Proces, který používá implicitní propojení, je ukončen operačním systémem, pokud se knihovna DLL při spuštění procesu nenašel. Proces, který používá explicitní propojení, se v této situaci neukončuje a může se pokoušet o obnovení z chyby. Proces může například upozornit uživatele na chybu a zadat jinou cestu ke knihovně DLL.
Proces, který používá implicitní propojení, je také ukončen, pokud některý z knihoven DLL, pro které je propojený, má
DllMainfunkci, která selže. Proces, který používá explicitní propojení, se v této situaci neukončuje.Aplikace, která implicitně odkazuje na mnoho knihoven DLL, se může pomalu spustit, protože Windows načte všechny knihovny DLL při načtení aplikace. Aby se zlepšil výkon při spuštění, může aplikace používat implicitní propojení knihoven DLL vyžadované ihned po načtení. K načtení dalších knihoven DLL může použít explicitní propojení pouze v případě, že je potřebujete.
Explicitní propojení eliminuje potřebu propojení aplikace pomocí knihovny importu. Pokud změny v knihovně DLL způsobí změnu exportu, nemusí se aplikace znovu propojet, pokud budou volat pomocí názvu funkce, a ne
GetProcAddressřadové hodnoty. Aplikace, které používají implicitní propojení, musí stále znovu vytvořit odkaz na změněnou knihovnu importu.
Tady jsou dvě nebezpečí explicitního propojení, o které byste měli vědět:
Pokud má knihovna DLL funkci vstupního bodu, operační systém volá funkci v
DllMainkontextu vlákna, které volaloLoadLibrary. Funkce vstupního bodu není volána, pokud je knihovna DLL již připojena k procesu z důvodu předchozího volání metody , které nemá odpovídající voláníLoadLibraryFreeLibraryfunkce. Explicitní propojení může způsobit problémy, pokud knihovna DLL používá funkci k inicializaci každého vlákna procesu, protože všechna vlákna, která již existují při volání (nebo ) nejsouDllMainLoadLibraryAfxLoadLibraryinicializována.Pokud knihovna DLL deklaruje data statického rozsahu jako , může způsobit chybu ochrany, pokud je
__declspec(thread)explicitně propojena. Po načtení knihovny DLL voláním metody způsobí chybu ochranyLoadLibrarypokaždé, když kód odkazuje na tato data. (Data statického rozsahu zahrnují globální i místní statické položky.) Proto byste se při vytváření knihovny DLL měli vyhnout použití místního úložiště vláken. Pokud ne, informujte uživatele knihovny DLL o potenciálních nástrahách dynamického načítání knihovny DLL. Další informace najdete v tématu Použití místního úložiště vláken v dynamické knihovně (Windows SDK).
Jak používat implicitní propojení
Chcete-li použít knihovnu DLL implicitním propojením, klientské spustitelné soubory musí získat tyto soubory od zprostředkovatele knihovny DLL:
Jeden nebo více souborů hlaviček (soubory .h), které obsahují deklarace exportovaných dat, funkcí a tříd jazyka C++ v knihovně DLL. Třídy, funkce a data exportovaná knihovnou DLL musí být všechny označeny
__declspec(dllimport)v souboru hlaviček. Další informace najdete v tématu dllexport, dllimport.Knihovnu importu pro propojení se spustitelným souborem. Linker vytvoří knihovnu importu při vytvoření knihovny DLL. Další informace najdete v tématu Soubory LIB jako vstup linkeru.
Skutečný soubor knihovny DLL.
Chcete-li používat data, funkce a třídy v knihovně DLL implicitním propojením, musí každý zdrojový soubor klienta obsahovat soubory hlaviček, které je deklarují. Z hlediska kódování jsou volání exportovaných funkcí stejně jako jakékoli jiné volání funkce.
Chcete-li sestavit spustitelný soubor klienta, je nutné propojit knihovnu importu knihovny DLL. Pokud používáte externí soubor pravidel nebo sestavovací systém, zadejte knihovnu importu společně s ostatními soubory objektů nebo knihovnami, které propojujete.
Operační systém musí být schopen najít soubor knihovny DLL při načtení volajícího spustitelného souboru. To znamená, že při instalaci aplikace musíte buď nasadit, nebo ověřit existenci knihovny DLL.
Jak explicitně propojit knihovnu DLL
Chcete-li použít knihovnu DLL explicitním propojením, musí aplikace provést volání funkce pro explicitní načtení knihovny DLL za běhu. Pokud chcete explicitně propojit knihovnu DLL, musí aplikace:
Pokud chcete načíst knihovnu DLL a získat popisovač modulu, zavolejte LoadLibraryEx nebo podobnou funkci.
Voláním metody GetProcAddress získáte ukazatel funkce na každou exportované funkci, kterou aplikace volá. Vzhledem k tomu, že aplikace volají funkce knihovny DLL prostřednictvím ukazatele, kompilátor negeneruje externí odkazy, takže není nutné vytvářet propojení s knihovnou importu. Musíte však mít příkaz nebo , který definuje signaturu volání
typedefusingexportovaných funkcí, které voláte.Po provedení práce s knihovnou DLL volejte knihovnu FreeLibrary.
Tato ukázková funkce například volá za účelem načtení knihovny DLL s názvem "MyDLL", voláním metody získá ukazatel na funkci s názvem LoadLibraryGetProcAddress "DLLFunc1", zavolá funkci a uloží výsledek a potom zavolá metodu pro uvolnění FreeLibrary knihovny DLL.
#include "windows.h"
typedef HRESULT (CALLBACK* LPFNDLLFUNC1)(DWORD,UINT*);
HRESULT LoadAndCallSomeFunction(DWORD dwParam1, UINT * puParam2)
{
HINSTANCE hDLL; // Handle to DLL
LPFNDLLFUNC1 lpfnDllFunc1; // Function pointer
HRESULT hrReturnVal;
hDLL = LoadLibrary("MyDLL");
if (NULL != hDLL)
{
lpfnDllFunc1 = (LPFNDLLFUNC1)GetProcAddress(hDLL, "DLLFunc1");
if (NULL != lpfnDllFunc1)
{
// call the function
hrReturnVal = lpfnDllFunc1(dwParam1, puParam2);
}
else
{
// report the error
hrReturnVal = ERROR_DELAY_LOAD_FAILED;
}
FreeLibrary(hDLL);
}
else
{
hrReturnVal = ERROR_DELAY_LOAD_FAILED;
}
return hrReturnVal;
}
Na rozdíl od tohoto příkladu byste ve většině případů měli volat a pouze jednou v aplikaci LoadLibraryFreeLibrary pro danou knihovnu DLL. To platí zejména v případě, že budete volat více funkcí v knihovně DLL nebo opakovaně volat funkce knihovny DLL.