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.
Pokud chcete provést konkrétní zpracování na základě názvů knihoven DLL nebo importů, můžete zadat vlastní verzi pomocné funkce.
Pomocná funkce provede tyto akce:
Zkontroluje uložený popisovač knihovny a zjistí, jestli už byl načten.
Volání
LoadLibrary
pokusu o načtení knihovny DLLVolání
GetProcAddress
k pokusu o získání adresy proceduryVrátí se do záznamu načtení importu zpoždění, který zavolá nyní načtený vstupní bod.
Pomocná funkce může za každou z následujících akcí volat zpět na hook oznámení ve vašem programu:
Při spuštění pomocné funkce
Těsně před
LoadLibrary
zavolání v pomocné funkciTěsně před
GetProcAddress
zavolání v pomocné funkciPokud volání
LoadLibrary
v pomocné funkci selžePokud volání
GetProcAddress
v pomocné funkci selžePo zpracování pomocné funkce
Každý z těchto bodů háku může vrátit hodnotu, která nějakým způsobem mění normální zpracování pomocné rutiny, s výjimkou návratu do bloku načítání zpoždění importu.
Výchozí pomocný kód najdete v delayhlp.cpp
adresáři MSVC a delayimp.h
v adresáři MSVC include
. Je zkompilován do delayimp.lib
adresáře MSVC lib
pro vaši cílovou architekturu. Tuto knihovnu budete muset zahrnout do kompilací, pokud nenapíšete vlastní pomocnou funkci.
Zpoždění konvencí volání, parametrů a návratového typu pomocné rutiny načítání
Prototyp rutiny pomocné rutiny pro zpoždění zatížení je:
FARPROC WINAPI __delayLoadHelper2(
PCImgDelayDescr pidd,
FARPROC * ppfnIATEntry
);
Parametry
pidd
const
Ukazatel na ImgDelayDescr
hodnotu, která obsahuje posuny různých dat souvisejících s importem, časové razítko pro informace vazby a sadu atributů, které poskytují další informace o obsahu popisovače. V současné době existuje pouze jeden atribut, který označuje, dlattrRva
že adresy v popisovači jsou relativní virtuální adresy. Další informace naleznete v deklarací v delayimp.h
.
Ukazatele v popisovači zpoždění (ImgDelayDescr
v delayimp.h
) používají relativní virtuální adresy (RVA) k práci podle očekávání v 32bitové i 64bitové programy. Chcete-li je použít, převeďte tyto RVA zpět na ukazatele pomocí funkce PFromRva
nalezené v delayhlp.cpp
. Tuto funkci můžete použít u každého pole v popisovači a převést je zpět na 32bitové nebo 64bitové ukazatele. Výchozí pomocná funkce pro načtení zpoždění je dobrou šablonou, která se používá jako příklad.
Definice struktury naleznete v PCImgDelayDescr
tématu Struktury a konstanty definice.
ppfnIATEntry
Ukazatel na slot v tabulce IAT (Delay Load Import Address Table). Je to slot, který se aktualizuje adresou importované funkce. Pomocná rutina musí uložit stejnou hodnotu, kterou vrací do tohoto umístění.
Očekávané návratové hodnoty
Pokud je pomocná funkce úspěšná, vrátí adresu importované funkce.
Pokud funkce selže, vyvolá strukturovanou výjimku a vrátí hodnotu 0. Mohou být vyvolány tři typy výjimek:
Neplatný parametr, ke kterému dochází v případě, že atributy nejsou
pidd
zadány správně. Považuje se za neopravitelnou chybu.LoadLibrary
v zadané knihovně DLL došlo k chybě.Selhání .
GetProcAddress
Za zpracování těchto výjimek je vaší zodpovědností. Další informace naleznete v tématu Zpracování chyb a oznámení.
Poznámky
Konvence volání pomocné funkce je __stdcall
. Typ návratové hodnoty není relevantní, proto FARPROC
se používá. Tato funkce má propojení jazyka C, což znamená, že je nutné ji zabalit extern "C"
, když je deklarována v kódu jazyka C++. Makro ExternC
se postará o tento obálku za vás.
Pokud chcete jako háček oznámení použít pomocnou rutinu, musí váš kód zadat odpovídající ukazatel funkce, který se má vrátit. Kód thunk, který linker vygeneruje, pak vezme vrácenou hodnotu jako skutečný cíl importu a přeskočí přímo na něj. Pokud nechcete jako hák oznámení použít pomocnou rutinu, uložte návratovou hodnotu pomocné funkce do ppfnIATEntry
umístění předaného ukazatele funkce.
Ukázková funkce háku
Následující kód ukazuje, jak implementovat základní funkci háku.
FARPROC WINAPI delayHook(unsigned dliNotify, PDelayLoadInfo pdli)
{
switch (dliNotify) {
case dliStartProcessing :
// If you want to return control to the helper, return 0.
// Otherwise, return a pointer to a FARPROC helper function
// that will be used instead, thereby bypassing the rest
// of the helper.
break;
case dliNotePreLoadLibrary :
// If you want to return control to the helper, return 0.
// Otherwise, return your own HMODULE to be used by the
// helper instead of having it call LoadLibrary itself.
break;
case dliNotePreGetProcAddress :
// If you want to return control to the helper, return 0.
// If you choose you may supply your own FARPROC function
// address and bypass the helper's call to GetProcAddress.
break;
case dliFailLoadLib :
// LoadLibrary failed.
// If you don't want to handle this failure yourself, return 0.
// In this case the helper will raise an exception
// (ERROR_MOD_NOT_FOUND) and exit.
// If you want to handle the failure by loading an alternate
// DLL (for example), then return the HMODULE for
// the alternate DLL. The helper will continue execution with
// this alternate DLL and attempt to find the
// requested entrypoint via GetProcAddress.
break;
case dliFailGetProc :
// GetProcAddress failed.
// If you don't want to handle this failure yourself, return 0.
// In this case the helper will raise an exception
// (ERROR_PROC_NOT_FOUND) and exit.
// If you choose, you may handle the failure by returning
// an alternate FARPROC function address.
break;
case dliNoteEndProcessing :
// This notification is called after all processing is done.
// There is no opportunity for modifying the helper's behavior
// at this point except by longjmp()/throw()/RaiseException.
// No return value is processed.
break;
default :
return NULL;
}
return NULL;
}
/*
and then at global scope somewhere:
ExternC const PfnDliHook __pfnDliNotifyHook2 = delayHook;
ExternC const PfnDliHook __pfnDliFailureHook2 = delayHook;
*/
Zpoždění struktury zatížení a definice konstant
Výchozí obslužná rutina pro načítání zpoždění používá několik struktur ke komunikaci s funkcemi háku a během jakýchkoli výjimek. Tyto struktury jsou definovány v delayimp.h
. Tady jsou makra, definice typedef, hodnoty oznámení a selhání, informační struktury a typ funkce pointer-to-hook předaný do háků:
#define _DELAY_IMP_VER 2
#if defined(__cplusplus)
#define ExternC extern "C"
#else
#define ExternC extern
#endif
typedef IMAGE_THUNK_DATA * PImgThunkData;
typedef const IMAGE_THUNK_DATA * PCImgThunkData;
typedef DWORD RVA;
typedef struct ImgDelayDescr {
DWORD grAttrs; // attributes
RVA rvaDLLName; // RVA to dll name
RVA rvaHmod; // RVA of module handle
RVA rvaIAT; // RVA of the IAT
RVA rvaINT; // RVA of the INT
RVA rvaBoundIAT; // RVA of the optional bound IAT
RVA rvaUnloadIAT; // RVA of optional copy of original IAT
DWORD dwTimeStamp; // 0 if not bound,
// O.W. date/time stamp of DLL bound to (Old BIND)
} ImgDelayDescr, * PImgDelayDescr;
typedef const ImgDelayDescr * PCImgDelayDescr;
enum DLAttr { // Delay Load Attributes
dlattrRva = 0x1, // RVAs are used instead of pointers
// Having this set indicates a VC7.0
// and above delay load descriptor.
};
//
// Delay load import hook notifications
//
enum {
dliStartProcessing, // used to bypass or note helper only
dliNoteStartProcessing = dliStartProcessing,
dliNotePreLoadLibrary, // called just before LoadLibrary, can
// override w/ new HMODULE return val
dliNotePreGetProcAddress, // called just before GetProcAddress, can
// override w/ new FARPROC return value
dliFailLoadLib, // failed to load library, fix it by
// returning a valid HMODULE
dliFailGetProc, // failed to get proc address, fix it by
// returning a valid FARPROC
dliNoteEndProcessing, // called after all processing is done, no
// bypass possible at this point except
// by longjmp()/throw()/RaiseException.
};
typedef struct DelayLoadProc {
BOOL fImportByName;
union {
LPCSTR szProcName;
DWORD dwOrdinal;
};
} DelayLoadProc;
typedef struct DelayLoadInfo {
DWORD cb; // size of structure
PCImgDelayDescr pidd; // raw form of data (everything is there)
FARPROC * ppfn; // points to address of function to load
LPCSTR szDll; // name of dll
DelayLoadProc dlp; // name or ordinal of procedure
HMODULE hmodCur; // the hInstance of the library we have loaded
FARPROC pfnCur; // the actual function that will be called
DWORD dwLastError;// error received (if an error notification)
} DelayLoadInfo, * PDelayLoadInfo;
typedef FARPROC (WINAPI *PfnDliHook)(
unsigned dliNotify,
PDelayLoadInfo pdli
);
Výpočet potřebných hodnot pro odložené načítání
Rutina pomocné rutiny načítání zpoždění potřebuje vypočítat dva důležité informace. Pro pomoc jsou k dispozici dvě vložené funkce delayhlp.cpp
pro výpočet těchto informací.
Nejprve
IndexFromPImgThunkData
vypočítá index aktuálního importu do tří různých tabulek (tabulka importu adres (IAT), vázaná tabulka adres importu (BIAT) a nevázaná tabulka adres importu (UIAT).Druhý,
CountOfImports
počítá počet importů v platnéM IAT.
// utility function for calculating the index of the current import
// for all the tables (INT, BIAT, UIAT, and IAT).
__inline unsigned
IndexFromPImgThunkData(PCImgThunkData pitdCur, PCImgThunkData pitdBase) {
return pitdCur - pitdBase;
}
// utility function for calculating the count of imports given the base
// of the IAT. NB: this only works on a valid IAT!
__inline unsigned
CountOfImports(PCImgThunkData pitdBase) {
unsigned cRet = 0;
PCImgThunkData pitd = pitdBase;
while (pitd->u1.Function) {
pitd++;
cRet++;
}
return cRet;
}
Podpora uvolnění knihovny DLL načtené zpožděním
Když se načte knihovna DLL načtená zpoždění, výchozí pomocné rutiny pro načtení zpoždění zkontroluje, jestli mají popisovače zpoždění a kopii původní tabulky adresy importu pUnloadIAT
(IAT) v poli. Pokud ano, pomocník uloží ukazatel do seznamu na popisovač zpoždění importu. Tato položka umožňuje pomocné funkci najít knihovnu DLL podle názvu, aby podporovala explicitní uvolnění této knihovny DLL.
Tady jsou přidružené struktury a funkce pro explicitní uvolnění knihovny DLL načtené zpožděním:
//
// Unload support from delayimp.h
//
// routine definition; takes a pointer to a name to unload
ExternC
BOOL WINAPI
__FUnloadDelayLoadedDLL2(LPCSTR szDll);
// structure definitions for the list of unload records
typedef struct UnloadInfo * PUnloadInfo;
typedef struct UnloadInfo {
PUnloadInfo puiNext;
PCImgDelayDescr pidd;
} UnloadInfo;
// from delayhlp.cpp
// the default delay load helper places the unloadinfo records in the
// list headed by the following pointer.
ExternC
PUnloadInfo __puiHead;
Struktura UnloadInfo
je implementována pomocí třídy C++, která používá LocalAlloc
a LocalFree
implementuje jako své operator new
a operator delete
v uvedeném pořadí. Tyto možnosti se uchovávají ve standardním propojeném seznamu, který se používá __puiHead
jako hlava seznamu.
Při volání __FUnloadDelayLoadedDLL
se pokusí najít název, který zadáte v seznamu načtených knihoven DLL. (Vyžaduje se přesná shoda.) Pokud se najde, zkopíruje se kopie IAT v pUnloadIAT
horní části spuštěného IAT, aby se obnovily ukazatele thunk. Pak se knihovna uvolní pomocí FreeLibrary
, odpovídající UnloadInfo
záznam se odpojí ze seznamu a odstraní a TRUE
vrátí se.
Argument funkce __FUnloadDelayLoadedDLL2
rozlišují malá a velká písmena. Zadáte například:
__FUnloadDelayLoadedDLL2("user32.dll");
a ne:
__FUnloadDelayLoadedDLL2("User32.DLL");
Příklad uvolnění knihovny DLL načtené zpožděním naleznete v tématu Explicitně uvolnění knihovny DLL se zpožděním.
Vývoj vlastní pomocné funkce pro načítání zpoždění
Můžete chtít zadat vlastní verzi pomocné rutiny pro odložení načítání. Ve své vlastní rutině můžete provádět konkrétní zpracování na základě názvů knihoven DLL nebo importů. Existují dva způsoby, jak vložit vlastní kód: Kód vlastní pomocné funkce, případně na základě dodaného kódu. Nebo připojte dodaného pomocníka, aby volal vlastní funkci pomocí oznámení.
Kódování vlastního pomocníka
Vytvoření vlastní pomocné rutiny je jednoduché. Existující kód můžete použít jako vodítko pro novou funkci. Vaše funkce musí používat stejné konvence volání jako stávající pomocník. A pokud se vrátí na bloky generované linkerem, musí vrátit správný ukazatel funkce. Jakmile vytvoříte kód, můžete hovor buď splnit, nebo se z hovoru dostat, ale rádi.
Použití háku oznámení pro zahájení zpracování
Pravděpodobně je nejjednodušší poskytnout nový ukazatel na funkci háku oznámení zadané uživatelem, která přebírá stejné hodnoty jako výchozí pomocník pro dliStartProcessing
oznámení. V tomto okamžiku se funkce háku může v podstatě stát novou pomocnou funkcí, protože úspěšný návrat do výchozí pomocné rutiny obchází veškeré další zpracování ve výchozím pomocném pomocníku.