遅延読み込みヘルパー関数について
実行時に DLL を実際に読み込むのは、リンカーでサポートされる遅延読み込みのヘルパー関数です。 ヘルパー関数を変更して、その動作をカスタマイズできます。 delayimp.lib
で指定されたヘルパー関数を使用する代わりに、独自の関数を記述し、プログラムにリンクします。 1 つのヘルパー関数が、遅延読み込みが行われたすべての DLL に対して機能します。
DLL やインポートの名前に基づいて特定の処理を実行する必要がある場合は、独自のバージョンのヘルパー関数を指定できます。
ヘルパー関数で実行されるアクションは次のとおりです。
既に読み込まれているかどうかを確認するために、ライブラリへの格納済みハンドルをチェックする
LoadLibrary
を呼び出して DLL の読み込みを試行するGetProcAddress
を呼び出してプロシージャのアドレスの取得を試行する現在読み込まれているエントリ ポイントを呼び出すために、遅延インポート読み込みサンクに戻る
ヘルパー関数では、次の各アクションの後に、プログラム内の通知フックにコールバックできます。
ヘルパー関数の開始時
ヘルパー関数で
LoadLibrary
が呼び出される直前ヘルパー関数で
GetProcAddress
が呼び出される直前ヘルパー関数での
LoadLibrary
への呼び出しが失敗した場合ヘルパー関数での
GetProcAddress
への呼び出しが失敗した場合ヘルパー関数の処理が完了した後
これらの各フックポイントでは、遅延インポート読み込みサンクへの戻り値を除き、何らかの形でヘルパー ルーチンの通常の処理を変更する値を返すことがあります。
既定のヘルパー コードは、MSVC の include
ディレクトリの delayhlp.cpp
と delayimp.h
に見つかります。 これは、ターゲット アーキテクチャの MSVC の lib
ディレクトリの delayimp.lib
にコンパイルされます。 独自のヘルパー関数を記述しない限り、このライブラリをコンパイルに指定する必要があります。
遅延読み込みヘルパーの呼び出し規則、パラメーター、および戻り値の型
遅延読み込みヘルパー ルーチンのプロトタイプは次のとおりです。
FARPROC WINAPI __delayLoadHelper2(
PCImgDelayDescr pidd,
FARPROC * ppfnIATEntry
);
パラメーター
pidd
さまざまなインポート関連のデータのオフセット、バインド情報のタイムスタンプ、および記述子コンテンツに関する追加情報を提供する属性セットが含まれる、const
への ImgDelayDescr
ポインター。 現在、記述子のアドレスが相対仮想アドレスであることを示す属性は dlattrRva
のみです。 詳細については、delayimp.h
の宣言を参照してください。
遅延記述子内のポインター (delayimp.h
内の ImgDelayDescr
) では、32 ビットと 64 ビットのプログラムの両方で期待どおりに動作するように、相対仮想アドレス (RVA) を使用します。 それらを使用するには、delayhlp.cpp
で見つかった関数 PFromRva
を使用して、それら RVA をポインターに変換します。 この関数を記述子内の各フィールドに対して使用して、32 ビットまたは 64 ビットのいずれかのポインターに変換することができます。 既定の遅延読み込みヘルパー関数は、例として使用するのに適したテンプレートです。
PCImgDelayDescr
構造体の定義については、構造体と定数の定義に関する記事を参照してください。
ppfnIATEntry
遅延読み込みインポート アドレス テーブル (IAT) 内のスロットへのポインター。 これは、インポートされた関数のアドレスを使用して更新されるスロットです。 ヘルパー ルーチンには、この場所に戻される値と同じ値を保存する必要があります。
予想戻り値
ヘルパー関数が成功した場合、インポートされた関数のアドレスを返します。
ヘルパー関数が失敗した場合、構造化された例外を発生させて 0 を返します。 発生される例外には 3 種類あります。
無効なパラメーター。
pidd
の属性が正しく指定されない場合に生じます。 これを回復不可能なエラーとして扱います。指定された DLL で
LoadLibrary
が失敗しました。GetProcAddress
の失敗。
これらの例外には自分で対処する必要があります。 詳細については、「エラー処理と通知」を参照してください。
解説
ヘルパー関数の呼び出し規則は __stdcall
です。 戻り値の種類が適切でないため、FARPROC
が使用されます。 この関数には C リンケージがあります。これは、C++ コードで宣言される場合に、extern "C"
でラップする必要があることを意味します。 このラッパーは ExternC
マクロによって自動的に処理されます。
ヘルパー ルーチンを通知フックとして使用するには、返される適切な関数ポインターをコードで指定する必要があります。 リンカーが生成するサンク コードは、その戻り値をインポートの実際のターゲットとして扱い、そこに直接ジャンプします。 ヘルパー ルーチンを通知フックとして使用しない場合は、ヘルパー関数の戻り値を 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
で定義されています。 フックに渡されるマクロ、typedef、通知とエラーの値、情報構造体、ポインターからフックへの関数の型を次に示します。
#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
);
遅延読み込みに必要な値を計算する
遅延読み込みヘルパー ルーチンでは、2 つの重要な情報を計算する必要があります。 この情報を計算するのに役立つインライン関数が delayhlp.cpp
に 2 つあります。
1 つ目の
IndexFromPImgThunkData
では、現在のインポートのインデックスから 3 つの異なるテーブル (インポート アドレス テーブル (IAT)、バインドされたインポート アドレス テーブル (BIAT)、バインドされていないインポート アドレス テーブル (UIAT)) を計算します。2 つ目の
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 が読み込まれると、既定の遅延読み込みヘルパーでは、遅延読み込み記述子にポインターと pUnloadIAT
フィールド内にある元のインポート アドレス テーブル (IAT) のコピーが含まれていることを確認します。 その場合、ヘルパーによってリスト内のポインターがインポート遅延記述子に保存されます。 このエントリにより、ヘルパー関数で 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
構造体は、operator new
と operator delete
としてそれぞれ LocalAlloc
と LocalFree
の実装が使用される C++ クラスを使用して実装されます。 それらのオプションは、__puiHead
がリストのヘッドとして使用される、標準のリンク リストに保持されます。
__FUnloadDelayLoadedDLL
を呼び出すと、読み込まれた DLL の一覧から、ユーザーが指定した名前の検索が試行されます。 (完全一致が必要です)。見つかった場合、 pUnloadIAT
の IAT のコピーが実行中の IAT の上部にコピーされ、サンク ポインターが復元されます。 その後、FreeLibrary
を使用してライブラリが解放され、一致する UnloadInfo
レコードのリストとのリンクが解除されて、TRUE
が返されます。
関数 __FUnloadDelayLoadedDLL2
に対する引数は、大文字と小文字が区別されます。 たとえば、次のように指定できます。
__FUnloadDelayLoadedDLL2("user32.dll");
次のようにはしません。
__FUnloadDelayLoadedDLL2("User32.DLL");
遅延読み込みが行われた DLL のアンロードの例については、「遅延読み込みが行われた DLL の明示的なアンロード」を参照してください。
独自の遅延読み込みヘルパー関数を開発する
独自のバージョンの遅延読み込みヘルパー ルーチンを用意することもできます。 独自のルーチンを使用すると、DLL またはインポートの名前に基づいて特定の処理を行うことができます。 独自のコードは 2 つの方法で挿入できます。指定されたコードに基づいて独自のヘルパー関数をコーディングします。 または、通知フックを使用して独自の関数を呼び出すよう指定されたヘルパーをフックします。
独自のヘルパーをコーディングする
独自のヘルパー ルーチンは簡単に作成できます。 既存のコードを新しい関数のガイドとして使用できます。 関数では、既存のヘルパーと同じ呼び出し規則を使用する必要があります。 そして、リンカーによって生成されたサンクに戻る場合は、適切な関数ポインターを返す必要があります。 コードの作成が完了したら、呼び出しを満たすか、呼び出しを抜けるか、いずれかにすることができます。
処理開始通知フックを使用する
おそらく最も簡単なのは、dliStartProcessing
通知の既定のヘルパーと同じ値を取る、ユーザー指定の通知フック関数に新しいポインターを指定することです。 その時点で、そのフック関数が実質的に新しいヘルパー関数になる可能性があります。既定のヘルパーに正常に戻されると、既定のヘルパーでの後続の処理がすべてバイパスされるためです。