Поделиться через


Вспомогательная функция для отложенной загрузки

Вспомогательной функцией для отложенной загрузки, поддерживаемой компоновщиком, является то, что фактически загружает библиотеку DLL во время выполнения. Вы можете изменить вспомогательную функцию, чтобы настроить его поведение. Вместо использования предоставленной вспомогательной функции, напишите собственную функцию delayimp.libи свяжите ее с программой. Одна вспомогающая функция обслуживает все загруженные библиотеки DLL с задержкой.

Вы можете указать собственную версию вспомогательной функции, если вы хотите выполнить определенную обработку на основе имен библиотеки DLL или импорта.

Вспомогательная функция выполняет следующие действия:

  • Проверяет хранимый дескриптор библиотеки, чтобы узнать, загружен ли он уже

  • Вызовы LoadLibrary для попытки загрузить библиотеку DLL

  • Вызовы GetProcAddress для получения адреса процедуры

  • Возвращается в точку импорта задержки импорта, чтобы вызвать загруженную точку входа

Вспомогательная функция может вернуться к перехватчику уведомлений в программе после каждого из следующих действий:

  • При запуске вспомогательной функции

  • LoadLibrary Перед вызовом в вспомогательной функции

  • GetProcAddress Перед вызовом в вспомогательной функции

  • Если вызов в вспомогательной функции завершается LoadLibrary сбоем

  • Если вызов в вспомогательной функции завершается GetProcAddress сбоем

  • После завершения обработки вспомогательной функции

Каждая из этих точек перехватчика может возвращать значение, которое изменяет обычную обработку вспомогательной подпрограммы каким-то образом, за исключением возврата к задержке импорта нагрузки.

Вспомогательный код по умолчанию можно найти в delayhlp.cpp каталоге MSVC include и delayimp.h в ней. Он компилируется delayimp.lib в каталог MSVC lib для целевой архитектуры. Вам потребуется включить эту библиотеку в компиляции, если вы не напишете собственную вспомогательные функции.

Задержка вспомогательных соглашений о вызовах, параметров и возвращаемых типов

Прототип вспомогательной подпрограммы задержки загрузки:

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

Параметры

pidd
Указатель const на ImgDelayDescr объект, содержащий смещения различных данных, связанных с импортом, метку времени для сведений о привязке и набор атрибутов, которые предоставляют дополнительные сведения о содержимом дескриптора. В настоящее время существует только один атрибут, dlattrRvaкоторый указывает, что адреса в дескрипторе являются относительными виртуальными адресами. Дополнительные сведения см. в объявлениях.delayimp.h

Указатели дескриптора задержки (ImgDelayDescr в delayimp.h) используют относительные виртуальные адреса (RVAs) для работы как ожидалось в 32-разрядных и 64-разрядных программах. Чтобы использовать их, преобразуйте эти RVA обратно в указатели с помощью функции PFromRva, найденной в delayhlp.cpp. Эту функцию можно использовать для каждого поля дескриптора, чтобы преобразовать их обратно в 32-разрядные или 64-разрядные указатели. Вспомогательной функцией задержки загрузки по умолчанию является хороший шаблон, используемый в качестве примера.

Определение структуры см. в PCImgDelayDescr разделе "Структура" и "Константные определения".

ppfnIATEntry
Указатель на слот в таблице адресов импорта задержки загрузки (IAT). Это слот, который обновляется с адресом импортированной функции. Вспомогательный подпрограмма должна хранить то же значение, что и в этом расположении.

Ожидаемые возвращаемые значения

Если вспомогающая функция выполнена успешно, он возвращает адрес импортированной функции.

Если функция завершается ошибкой, она вызывает структурированное исключение и возвращает значение 0. Возможны три типа исключений.

  • Недопустимый параметр. Это исключение создается, если атрибуты в pidd указаны некорректно. Эта ошибка рассматривается как неустранимая ошибка.

  • Функции LoadLibrary не удалось загрузить указанную библиотеку DLL.

  • Ошибка в функции GetProcAddress.

Это ваша ответственность за обработку этих исключений. Дополнительные сведения см. в разделе об обработке ошибок и уведомлениях.

Замечания

Для вспомогательной функции используется соглашение о вызовах __stdcall. Тип возвращаемого значения не имеет значения, поэтому FARPROC используется. Эта функция имеет компоновку C, что означает, что она должна быть упакована extern "C" при объявлении в коде 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. Ниже приведены макросы, 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 реализуется с помощью класса C++, который использует LocalAlloc и LocalFree реализуется в качестве его operator new и operator deleteсоответственно. Эти параметры хранятся в стандартном связанном списке, который используется __puiHead в качестве главы списка.

При вызове __FUnloadDelayLoadedDLLон пытается найти имя, указанное в списке загруженных БИБЛИОТЕК DLL. (Требуется точное совпадение.) При обнаружении копия IAT копируется в верхней части запущенного IAT pUnloadIAT для восстановления указателей на thunk. Затем библиотека освобождается с помощью FreeLibrary, соответствующая UnloadInfo запись не связана со списком и удаляется и TRUE возвращается.

Аргумент функции __FUnloadDelayLoadedDLL2 учитывает регистр. Например, можно указать:

__FUnloadDelayLoadedDLL2("user32.dll");

и нет:

__FUnloadDelayLoadedDLL2("User32.DLL");

Пример выгрузки библиотеки DLL, загруженной с задержкой, см. в статье Явно выгрузить библиотеку DLL с задержкой.

Разработка собственной вспомогательной функции задержки загрузки

Возможно, потребуется предоставить собственную версию вспомогательной подпрограммы задержки загрузки. В собственной подпрограмме можно выполнять конкретную обработку на основе имен библиотеки DLL или импорта. Существует два способа вставки собственного кода: код собственной вспомогательной функции, возможно, на основе предоставленного кода. Или перехватчик предоставленной вспомогательной функции, чтобы вызвать собственную функцию с помощью перехватчиков уведомлений.

Код собственного вспомогательного помощника

Создание собственной вспомогательной подпрограммы просто. Существующий код можно использовать в качестве руководства для новой функции. Функция должна использовать те же соглашения о вызовах, что и существующий вспомогательный элемент. И, если он возвращается в созданные компоновщиком thunks, он должен вернуть правильный указатель функции. После создания кода можно либо выполнить вызов, либо выйти из вызова, но вам нравится.

Использование перехватчика уведомлений о начале обработки

Вероятно, проще всего указать новый указатель на функцию перехватчика уведомлений, предоставляемую пользователем, которая принимает те же значения, что и вспомогательный элемент по умолчанию для dliStartProcessing уведомления. На этом этапе функция перехватчика может по сути стать новой вспомогательной функцией, так как успешное возвращение вспомогательной функции по умолчанию проходит все дальнейшие обработки в вспомогательном приложении по умолчанию.

См. также

Поддержка компоновщика для dll с задержкой