Bagikan melalui


Memahami fungsi pembantu beban penundaan

Fungsi pembantu untuk pemuatan tertunda yang didukung linker adalah apa yang sebenarnya memuat DLL saat runtime. Anda dapat mengubah fungsi pembantu untuk menyesuaikan perilakunya. Alih-alih menggunakan fungsi pembantu yang disediakan di delayimp.lib, tulis fungsi Anda sendiri dan tautkan ke program Anda. Satu fungsi pembantu melayani semua DLL yang dimuat keterlambatan.

Anda dapat menyediakan versi fungsi pembantu Anda sendiri jika Anda ingin melakukan pemrosesan tertentu berdasarkan nama DLL atau impor.

Fungsi pembantu mengambil tindakan ini:

  • Memeriksa handel tersimpan ke pustaka untuk melihat apakah handel tersebut telah dimuat

  • LoadLibrary Panggilan untuk mencoba memuat DLL

  • GetProcAddress Panggilan untuk mencoba mendapatkan alamat prosedur

  • Kembali ke thunk muatan impor penundaan untuk memanggil titik masuk yang sekarang dimuat

Fungsi pembantu dapat memanggil kembali ke hook pemberitahuan di program Anda setelah setiap tindakan berikut:

  • Ketika fungsi pembantu dimulai

  • Tepat sebelum LoadLibrary dipanggil dalam fungsi pembantu

  • Tepat sebelum GetProcAddress dipanggil dalam fungsi pembantu

  • Jika panggilan ke LoadLibrary dalam fungsi pembantu gagal

  • Jika panggilan ke GetProcAddress dalam fungsi pembantu gagal

  • Setelah fungsi pembantu selesai diproses

Masing-masing titik kait ini dapat mengembalikan nilai yang mengubah pemrosesan normal rutinitas pembantu dengan cara tertentu, kecuali kembali ke thunk beban impor penundaan.

Kode pembantu default dapat ditemukan di delayhlp.cpp dan delayimp.h di direktori MSVC include . Ini dikompilasi ke delayimp.lib dalam direktori MSVC lib untuk arsitektur target Anda. Anda harus menyertakan pustaka ini dalam kompilasi Anda kecuali Anda menulis fungsi pembantu Anda sendiri.

Tunda konvensi panggilan pembantu beban, parameter, dan jenis pengembalian

Prototipe untuk rutinitas pembantu beban penundaan adalah:

FARPROC WINAPI __delayLoadHelper2(
    PCImgDelayDescr pidd,
    FARPROC * ppfnIATEntry
);

Parameter

pidd
const Penunjuk ke ImgDelayDescr yang berisi offset berbagai data terkait impor, tanda waktu untuk informasi pengikatan, dan sekumpulan atribut yang memberikan informasi lebih lanjut tentang konten deskriptor. Saat ini hanya ada satu atribut, dlattrRva, yang menunjukkan bahwa alamat dalam deskriptor adalah alamat virtual relatif. Untuk informasi selengkapnya, lihat deklarasi di delayimp.h.

Penunjuk dalam deskriptor penundaan (ImgDelayDescr dalam delayimp.h) menggunakan alamat virtual relatif (RVA) untuk bekerja seperti yang diharapkan dalam program 32-bit dan 64-bit. Untuk menggunakannya, konversikan RVA ini kembali ke penunjuk dengan menggunakan fungsi PFromRva, ditemukan di delayhlp.cpp. Anda dapat menggunakan fungsi ini pada setiap bidang dalam deskriptor untuk mengonversinya kembali ke pointer 32-bit atau 64-bit. Fungsi pembantu beban penundaan default adalah templat yang baik untuk digunakan sebagai contoh.

Untuk definisi PCImgDelayDescr struktur, lihat Struktur dan definisi konstanta.

ppfnIATEntry
Penunjuk ke slot dalam tabel alamat impor beban penundaan (IAT). Ini adalah slot yang diperbarui dengan alamat fungsi yang diimpor. Rutinitas pembantu perlu menyimpan nilai yang sama dengan yang dikembalikan ke lokasi ini.

Nilai pengembalian yang diharapkan

Jika fungsi pembantu berhasil, fungsi tersebut mengembalikan alamat fungsi yang diimpor.

Jika fungsi gagal, fungsi akan memunculkan pengecualian terstruktur dan mengembalikan 0. Tiga jenis pengecualian dapat dinaikkan:

  • Parameter tidak valid, yang terjadi jika atribut di pidd tidak ditentukan dengan benar. Perlakukan ini sebagai kesalahan yang tidak dapat dipulihkan.

  • LoadLibrary gagal pada DLL yang ditentukan.

  • GetProcAddressKegagalan .

Anda bertanggung jawab untuk menangani pengecualian ini. Untuk informasi selengkapnya, lihat Penanganan kesalahan dan pemberitahuan.

Keterangan

Konvensi panggilan untuk fungsi pembantu adalah __stdcall. Jenis nilai yang dikembalikan tidak relevan, jadi FARPROC digunakan. Fungsi ini memiliki tautan C, yang berarti perlu dibungkus oleh extern "C" ketika dideklarasikan dalam kode C++. ExternC Makro mengurus pembungkus ini untuk Anda.

Untuk menggunakan rutinitas pembantu Anda sebagai kait pemberitahuan, kode Anda harus menentukan penunjuk fungsi yang sesuai untuk dikembalikan. Kode thunk yang dihasilkan linker kemudian mengambil nilai pengembalian tersebut sebagai target nyata dari impor dan melompat langsung ke dalamnya. Jika Anda tidak ingin menggunakan rutinitas pembantu Anda sebagai kait pemberitahuan, simpan nilai pengembalian fungsi pembantu di ppfnIATEntry, lokasi penunjuk fungsi yang diteruskan.

Contoh fungsi kait

Kode berikut menunjukkan cara mengimplementasikan fungsi kait dasar.

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;
*/

Menunda struktur beban dan definisi konstanta

Rutinitas pembantu beban penundaan default menggunakan beberapa struktur untuk berkomunikasi dengan fungsi kait dan selama pengecualian apa pun. Struktur ini didefinisikan dalam delayimp.h. Berikut adalah makro, typedefs, nilai pemberitahuan dan kegagalan, struktur informasi, dan jenis pointer-to-hook-function yang diteruskan ke hook:

#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
    );

Menghitung nilai yang diperlukan untuk pemuatan penundaan

Rutinitas pembantu beban penundaan perlu menghitung dua informasi penting. Untuk membantu, ada dua fungsi sebaris untuk delayhlp.cpp menghitung informasi ini.

  • Yang pertama, IndexFromPImgThunkData, menghitung indeks impor saat ini ke dalam tiga tabel berbeda (tabel alamat impor (IAT), tabel alamat impor terikat (BIAT), dan tabel alamat impor tidak terikat (UIAT)).

  • Yang kedua, CountOfImports, menghitung jumlah impor dalam IAT yang valid.

// 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;
    }

Mendukung pembongkaran DLL yang dimuat keterlambatan

Ketika DLL yang dimuat keterlambatan dimuat, pembantu beban penundaan default memeriksa untuk melihat apakah deskriptor beban penundaan memiliki penunjuk dan salinan tabel alamat impor asli (IAT) di pUnloadIAT bidang . Jika demikian, pembantu menyimpan penunjuk dalam daftar ke deskriptor penundaan impor. Entri ini memungkinkan fungsi pembantu menemukan DLL berdasarkan nama, untuk mendukung pembongkaran DLL tersebut secara eksplisit.

Berikut adalah struktur dan fungsi terkait untuk secara eksplisit membongkar DLL yang dimuat keterlambatan:

//
// 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;

Struktur diimplementasikan UnloadInfo menggunakan kelas C++ yang menggunakan LocalAlloc dan LocalFree implementasi sebagai operator new dan operator delete, masing-masing. Opsi ini disimpan dalam daftar tertaut standar yang menggunakan __puiHead sebagai kepala daftar.

Saat Anda memanggil __FUnloadDelayLoadedDLL, ini mencoba menemukan nama yang Anda berikan dalam daftar DLL yang dimuat. (Diperlukan kecocokan yang tepat.) Jika ditemukan, salinan IAT di disalin di pUnloadIAT atas IAT yang sedang berjalan untuk memulihkan pointer thunk. Kemudian, pustaka dikosongkan dengan menggunakan FreeLibrary, rekaman yang UnloadInfo cocok dilepas tautannya dari daftar dan dihapus, dan TRUE dikembalikan.

Argumen untuk fungsi __FUnloadDelayLoadedDLL2 peka huruf besar/kecil. Misalnya, Anda akan menentukan:

__FUnloadDelayLoadedDLL2("user32.dll");

dan bukan:

__FUnloadDelayLoadedDLL2("User32.DLL");

Untuk contoh membongkar DLL yang dimuat keterlambatan, lihat Secara eksplisit membongkar DLL yang dimuat keterlambatan.

Mengembangkan fungsi pembantu beban penundaan Anda sendiri

Anda mungkin ingin memberikan versi rutinitas pembantu beban penundaan Anda sendiri. Dalam rutinitas Anda sendiri, Anda dapat melakukan pemrosesan tertentu berdasarkan nama DLL atau impor. Ada dua cara untuk memasukkan kode Anda sendiri: Kode fungsi pembantu Anda sendiri, mungkin berdasarkan kode yang disediakan. Atau, kaitkan pembantu yang disediakan untuk memanggil fungsi Anda sendiri dengan menggunakan kait pemberitahuan.

Kode pembantu Anda sendiri

Membuat rutinitas pembantu Anda sendiri sangat mudah. Anda dapat menggunakan kode yang ada sebagai panduan untuk fungsi baru Anda. Fungsi Anda harus menggunakan konvensi panggilan yang sama dengan pembantu yang ada. Dan, jika kembali ke thunk yang dihasilkan linker, itu harus mengembalikan penunjuk fungsi yang tepat. Setelah membuat kode, Anda dapat memenuhi panggilan atau keluar dari panggilan, sesuka Anda.

Gunakan kait pemberitahuan mulai pemrosesan

Mungkin paling mudah untuk memberikan penunjuk baru ke fungsi kait pemberitahuan yang disediakan pengguna yang mengambil nilai yang sama dengan pembantu default untuk dliStartProcessing pemberitahuan. Pada saat itu, fungsi kait pada dasarnya dapat menjadi fungsi pembantu baru, karena pengembalian yang berhasil ke pembantu default melewati semua pemrosesan lebih lanjut di pembantu default.

Baca juga

Dukungan linker untuk DLL yang dimuat keterlambatan