次の方法で共有


遅延読み込みヘルパー関数について

実行時に DLL を実際に読み込むのは、リンカーでサポートされる遅延読み込みのヘルパー関数です。 ヘルパー関数を変更して、その動作をカスタマイズできます。 delayimp.lib で指定されたヘルパー関数を使用する代わりに、独自の関数を記述し、プログラムにリンクします。 1 つのヘルパー関数が、遅延読み込みが行われたすべての DLL に対して機能します。

DLL やインポートの名前に基づいて特定の処理を実行する必要がある場合は、独自のバージョンのヘルパー関数を指定できます。

ヘルパー関数で実行されるアクションは次のとおりです。

  • 既に読み込まれているかどうかを確認するために、ライブラリへの格納済みハンドルをチェックする

  • LoadLibrary を呼び出して DLL の読み込みを試行する

  • GetProcAddress を呼び出してプロシージャのアドレスの取得を試行する

  • 現在読み込まれているエントリ ポイントを呼び出すために、遅延インポート読み込みサンクに戻る

ヘルパー関数では、次の各アクションの後に、プログラム内の通知フックにコールバックできます。

  • ヘルパー関数の開始時

  • ヘルパー関数で LoadLibrary が呼び出される直前

  • ヘルパー関数で GetProcAddress が呼び出される直前

  • ヘルパー関数での LoadLibrary への呼び出しが失敗した場合

  • ヘルパー関数での GetProcAddress への呼び出しが失敗した場合

  • ヘルパー関数の処理が完了した後

これらの各フックポイントでは、遅延インポート読み込みサンクへの戻り値を除き、何らかの形でヘルパー ルーチンの通常の処理を変更する値を返すことがあります。

既定のヘルパー コードは、MSVC の include ディレクトリの delayhlp.cppdelayimp.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 newoperator delete としてそれぞれ LocalAllocLocalFree の実装が使用される C++ クラスを使用して実装されます。 それらのオプションは、__puiHead がリストのヘッドとして使用される、標準のリンク リストに保持されます。

__FUnloadDelayLoadedDLL を呼び出すと、読み込まれた DLL の一覧から、ユーザーが指定した名前の検索が試行されます。 (完全一致が必要です)。見つかった場合、 pUnloadIAT の IAT のコピーが実行中の IAT の上部にコピーされ、サンク ポインターが復元されます。 その後、FreeLibrary を使用してライブラリが解放され、一致する UnloadInfo レコードのリストとのリンクが解除されて、TRUE が返されます。

関数 __FUnloadDelayLoadedDLL2 に対する引数は、大文字と小文字が区別されます。 たとえば、次のように指定できます。

__FUnloadDelayLoadedDLL2("user32.dll");

次のようにはしません。

__FUnloadDelayLoadedDLL2("User32.DLL");

遅延読み込みが行われた DLL のアンロードの例については、「遅延読み込みが行われた DLL の明示的なアンロード」を参照してください。

独自の遅延読み込みヘルパー関数を開発する

独自のバージョンの遅延読み込みヘルパー ルーチンを用意することもできます。 独自のルーチンを使用すると、DLL またはインポートの名前に基づいて特定の処理を行うことができます。 独自のコードは 2 つの方法で挿入できます。指定されたコードに基づいて独自のヘルパー関数をコーディングします。 または、通知フックを使用して独自の関数を呼び出すよう指定されたヘルパーをフックします。

独自のヘルパーをコーディングする

独自のヘルパー ルーチンは簡単に作成できます。 既存のコードを新しい関数のガイドとして使用できます。 関数では、既存のヘルパーと同じ呼び出し規則を使用する必要があります。 そして、リンカーによって生成されたサンクに戻る場合は、適切な関数ポインターを返す必要があります。 コードの作成が完了したら、呼び出しを満たすか、呼び出しを抜けるか、いずれかにすることができます。

処理開始通知フックを使用する

おそらく最も簡単なのは、dliStartProcessing 通知の既定のヘルパーと同じ値を取る、ユーザー指定の通知フック関数に新しいポインターを指定することです。 その時点で、そのフック関数が実質的に新しいヘルパー関数になる可能性があります。既定のヘルパーに正常に戻されると、既定のヘルパーでの後続の処理がすべてバイパスされるためです。

関連項目

遅延読み込み DLL のリンカーサポート