了解延遲載入協助程式函式
鏈接器支援的延遲載入的協助程式函式是實際在運行時間載入 DLL 的功能。 您可以修改協助程式函式來自定義其行為。 不要在 中使用 delayimp.lib
提供的協助程式函式,而是撰寫您自己的函式,並將它連結至您的程式。 一個協助程式函式會提供所有延遲載入的 DLL。
如果您想要根據 DLL 或匯入的名稱執行特定處理,您可以提供自己的協助程式函式版本。
協助程式函式會採取下列動作:
檢查連結庫的預存句柄,以查看它是否已載入
嘗試載入 DLL 的呼叫
LoadLibrary
嘗試取得程式位址的呼叫
GetProcAddress
返回延遲匯入載入 Thunk 以呼叫現在載入的進入點
協助程式函式可以在下列每個動作之後,回呼程式中的通知攔截:
協助程式函式啟動時
就在協助程式函式中呼叫之前
LoadLibrary
就在協助程式函式中呼叫之前
GetProcAddress
如果協助程式函式中的呼叫
LoadLibrary
失敗如果協助程式函式中的呼叫
GetProcAddress
失敗協助程式函式完成處理之後
除了返回延遲匯入載入 Thunk 之外,每個攔截點都可以傳回一個值,以某種方式改變協助程式例程的正常處理。
您可以在 MSVC include
目錄中和 delayimp.h
中找到delayhlp.cpp
預設協助程式程式代碼。 它會在目標架構的 MSVC lib
目錄中編譯成 delayimp.lib
。 除非您撰寫自己的協助程式函式,否則您必須在編譯中包含此連結庫。
延遲載入協助程式呼叫慣例、參數和傳回類型
延遲載入協助程式例程的原型為:
FARPROC WINAPI __delayLoadHelper2(
PCImgDelayDescr pidd,
FARPROC * ppfnIATEntry
);
參數
pidd
const
的指標ImgDelayDescr
,其中包含各種匯入相關數據的位移、系結信息的時間戳,以及提供描述元內容進一步資訊的屬性集。 目前只有一個屬性, dlattrRva
表示描述元中的位址是相對虛擬位址。 如需詳細資訊,請參閱 中的 delayimp.h
宣告。
延遲描述元中的指標會ImgDelayDescr
delayimp.h
使用相對虛擬位址 (RVA) 在32位和64位程式中如預期般運作。 若要使用它們,請使用 中找到delayhlp.cpp
的 函PFromRva
式,將這些 RVA 轉換回指標。 您可以在描述元中的每個欄位上使用這個函式,將它們轉換成 32 位或 64 位指標。 默認延遲載入協助程式函式是很好的範本,可用來作為範例。
如需結構的定義 PCImgDelayDescr
,請參閱 結構和常數定義。
ppfnIATEntry
延遲載入位址表格中位置的指標(IAT)。 這是使用匯入函式位址更新的位置。 協助程式例程必須儲存其傳回此位置的相同值。
預期的傳回值
如果協助程式函式成功,它會傳回匯入函式的位址。
如果函式失敗,則會引發結構化例外狀況並傳回0。 可以引發的例外狀況有三種:
參數無效,發生在
pidd
中的屬性未正確指定時。 將此視為無法復原的錯誤。指定 DLL 上的
LoadLibrary
失敗GetProcAddress
失敗。
您必須負責處理這些例外狀況。 如需詳細資訊,請參閱 錯誤處理和通知。
備註
Helper 函式的呼叫慣例是 __stdcall
。 傳回值的型別不相關,因此 FARPROC
會使用。 此函式具有 C 連結,這表示在 C++ 程式代碼中宣告時,必須加以包裝 extern "C"
。 ExternC
巨集會為您處理這個包裝函式。
若要使用您的協助程式例程作為通知攔截,您的程式代碼必須指定適當的函式指標以傳回。 連結器產生的 Thunk 程式碼便會以該傳回值作為匯入的真實目標,並直接跳到該處。 如果您不想使用協助程式例程做為通知攔截,請將協助程式函式的傳回值儲存在 ppfnIATEntry
中,傳入的函式指標位置。
範例攔截函式
下列程式代碼示範如何實作基本攔截函式。
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;
*/
延遲載入結構和常數定義
默認延遲載入協助程式例程會使用數個結構來與攔截函式通訊,並在任何例外狀況期間進行通訊。 這些結構定義於 中 delayimp.h
。 以下是傳遞至攔截的巨集、typedefs、通知和失敗值、信息結構和指針對勾函式類型:
#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
);
計算延遲載入的必要值
延遲載入協助程式例程需要計算兩個重要資訊片段。 為了協助,中有 delayhlp.cpp
兩個內嵌函式可用來計算這項資訊。
第一個
IndexFromPImgThunkData
,會計算目前匯入到三個不同數據表的索引(匯入位址表(IAT)、系結匯入位址表(BIAT),以及未系結的匯入地址表(UIAT)。第二個,
CountOfImports
會計算有效 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;
}
支援卸除延遲載入的 DLL
載入延遲載入 DLL 時,預設延遲載入協助程式會檢查延遲載入描述項是否有指標,以及欄位中原始匯入位址表 (IAT) pUnloadIAT
的複本。 如果是,協助程式會將指標儲存在清單中,以匯入延遲描述元。 這個專案可讓協助程式函式依名稱尋找 DLL,以支援明確卸除該 DLL。
以下是明確卸除延遲載入 DLL 的相關結構和函式:
//
// 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;
結構UnloadInfo
是使用分別使用 LocalAlloc
和實作做為其 operator new
和 LocalFree
operator delete
的C++類別來實作。 這些選項會保留在作為 __puiHead
清單前端的標準連結清單中。
當您呼叫 __FUnloadDelayLoadedDLL
時,它會嘗試在載入的 DLL 清單中尋找您提供的名稱。 (需要完全相符專案。如果找到,中的 pUnloadIAT
IAT 複本會複製到執行中的 IAT 頂端,以還原 Thunk 指標。 然後,使用 FreeLibrary
釋放連結庫,比 UnloadInfo
對記錄會從清單取消連結並刪除,並 TRUE
傳回。
函式的 __FUnloadDelayLoadedDLL2
自變數會區分大小寫。 例如,您可以指定:
__FUnloadDelayLoadedDLL2("user32.dll");
和 not:
__FUnloadDelayLoadedDLL2("User32.DLL");
如需卸除延遲載入 DLL 的範例,請參閱 明確卸除延遲載入的 DLL。
開發您自己的延遲載入協助程式函式
您可能想要提供自己的延遲載入協助程式例程版本。 在您自己的例程中,您可以根據 DLL 的名稱或匯入來執行特定處理。 有兩種方式可以插入您自己的程式代碼:根據提供的程式代碼撰寫您自己的協助程式函式。 或者,使用通知攔截來連結提供的協助程式來呼叫您自己的函 式。
撰寫您自己的協助程式程式代碼
建立您自己的協助程式例程很簡單。 您可以使用現有的程式代碼作為新函式的指南。 您的函式必須使用與現有協助程式相同的呼叫慣例。 而且,如果它返回連結器產生的 Thunks,它必須傳回適當的函式指標。 建立程式代碼之後,您可以滿足呼叫或離開呼叫,但您想要。
使用開始處理通知攔截
提供使用者提供的通知攔截函式的新指標可能最容易,其接受與通知的默認協助程式 dliStartProcessing
相同的值。 此時,攔截函式基本上可以成為新的協助程式函式,因為成功傳回默認協助程式會略過默認協助程式中的所有進一步處理。