Поддержка компоновщика для библиотек DLL с отложенной загрузкой

Компоновщик MSVC поддерживает задержку загрузки библиотек DLL. Эта функция устраняет необходимость использования функций LoadLibrary Windows SDK и GetProcAddress реализации отложенной загрузки библиотеки DLL.

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

При задержке загрузки при неявном связывании библиотеки DLL компоновщик предоставляет параметры для задержки загрузки БИБЛИОТЕКИ, пока программа не вызовет функцию в этой библиотеке DLL.

Приложение может отложить загрузку библиотеки DLL с помощью /DELAYLOAD параметра компоновщика (задержка импорта загрузки) с вспомогательной функцией. (Реализация вспомогательной функции по умолчанию предоставляется корпорацией Майкрософт.) Вспомогательная функция загружает библиотеку DLL по запросу во время выполнения, вызывая LoadLibrary и GetProcAddress для вас.

Рассмотрите возможность задержки загрузки библиотеки DLL, если:

  • Программа может не вызывать функцию в библиотеке DLL.

  • Функция в библиотеке DLL может не вызываться до конца выполнения программы.

Задержка загрузки библиотеки DLL может быть указана во время сборки проекта EXE или DLL. Проект DLL, который задерживает загрузку одного или нескольких БИБЛИОТЕК DLL, не должен вызывать точку DllMainвхода с задержкой.

Задание библиотек DLL с отложенной загрузкой

Вы можете указать библиотеки DLL для задержки загрузки с помощью /delayload:dllname параметра компоновщика. Если вы не планируете использовать собственную версию вспомогательной функции, необходимо также связать программу с delayimp.lib (для классических приложений) или dloadhelper.lib (для приложений UWP).

Ниже приведен простой пример задержки загрузки библиотеки DLL:

// cl t.cpp user32.lib delayimp.lib  /link /DELAYLOAD:user32.dll
#include <windows.h>
// uncomment these lines to remove .libs from command line
// #pragma comment(lib, "delayimp")
// #pragma comment(lib, "user32")

int main() {
   // user32.dll will load at this point
   MessageBox(NULL, "Hello", "Hello", MB_OK);
}

Создание ОТЛАДОЧНОЙ версии проекта. Пошаговое руководство по коду с помощью отладчика и вы заметите, что user32.dll загружается только при вызове MessageBox.

Явная выгрузка библиотеки DLL с отложенной загрузкой

Параметр /delay:unload компоновщика позволяет коду явно выгрузить библиотеку DLL, которая была загружена. По умолчанию импорт, загруженный с задержкой, остается в таблице адресов импорта (IAT). Однако если вы используете /delay:unload в командной строке компоновщика, вспомогательная функция поддерживает явную выгрузку библиотеки DLL с помощью __FUnloadDelayLoadedDLL2 вызова и сбрасывает IAT в исходную форму. Теперь недопустимые указатели перезаписываются. IAT — это поле в ImgDelayDescr структуре, содержащей адрес копии исходного IAT, если он существует.

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

В этом примере показано, как явно выгрузить библиотеку DLL, MyDll.dllсодержащую функцию fnMyDll:

// link with /link /DELAYLOAD:MyDLL.dll /DELAY:UNLOAD
#include <windows.h>
#include <delayimp.h>
#include "MyDll.h"
#include <stdio.h>

#pragma comment(lib, "delayimp")
#pragma comment(lib, "MyDll")
int main()
{
    BOOL TestReturn;
    // MyDLL.DLL will load at this point
    fnMyDll();

    //MyDLL.dll will unload at this point
    TestReturn = __FUnloadDelayLoadedDLL2("MyDll.dll");

    if (TestReturn)
        printf_s("\nDLL was unloaded");
    else
        printf_s("\nDLL was not unloaded");
}

Важные заметки о выгрузке библиотеки DLL с задержкой:

  • Реализацию __FUnloadDelayLoadedDLL2 функции можно найти в файле delayhlp.cppв каталоге MSVC include . Дополнительные сведения см. в разделе "Общие сведения о вспомогательной функции задержки загрузки".

  • Параметр name__FUnloadDelayLoadedDLL2 функции должен точно соответствовать (включая регистр), который содержит библиотеку импорта. (Эта строка также находится в таблице импорта в изображении.) Содержимое библиотеки импорта можно просмотреть с помощью DUMPBIN /DEPENDENTS. Если вы предпочитаете сопоставление строки без учета регистра, можно обновить __FUnloadDelayLoadedDLL2 для использования одной из нечувствительных строковых функций CRT регистра или вызова API Windows.

Привязка импорта, загруженного с задержкой

Поведение компоновщика по умолчанию — создание привязываемой таблицы адресов импорта (IAT) для библиотеки DLL с задержкой. Если библиотека DLL привязана, вспомогательной функции пытается использовать привязанные сведения вместо вызова GetProcAddress каждого из указанных импортов. Если метка времени или предпочтительный адрес не совпадает с загруженным библиотекой DLL, функция вспомогательной функции предполагает, что связанная таблица адресов импорта устарела. Он продолжается, как если бы IAT не существует.

Если вы никогда не планируете привязать импорт dll с задержкой, укажите /delay:nobind в командной строке компоновщика. Компоновщик не создаст связанную таблицу адресов импорта, которая экономит место в файле изображения.

Загрузка всех единиц импорта для DLL с отложенной загрузкой

Функция __HrLoadAllImportsForDll , определенная в delayhlp.cpp, сообщает компоновщику загружать все импорты из библиотеки DLL, указанной с параметром /delayload компоновщика.

При загрузке всех импортов одновременно можно централизованно обрабатывать ошибки в одном месте. Можно избежать структурированной обработки исключений во всех фактических вызовах импорта. Это также позволяет избежать ситуации, когда приложение завершается сбоем в процессе: например, если вспомогательный код не загружает импорт, после успешной загрузки других пользователей.

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

__HrLoadAllImportsForDll делает сравнение регистра с именем, хранящимся внутри самой библиотеки DLL.

Ниже приведен пример, который используется __HrLoadAllImportsForDll в функции, вызываемой TryDelayLoadAllImports для попытки загрузить именованную библиотеку DLL. Она использует функцию для CheckDelayExceptionопределения поведения исключений.

int CheckDelayException(int exception_value)
{
    if (exception_value == VcppException(ERROR_SEVERITY_ERROR, ERROR_MOD_NOT_FOUND) ||
        exception_value == VcppException(ERROR_SEVERITY_ERROR, ERROR_PROC_NOT_FOUND))
    {
        // This example just executes the handler.
        return EXCEPTION_EXECUTE_HANDLER;
    }
    // Don't attempt to handle other errors
    return EXCEPTION_CONTINUE_SEARCH;
}

bool TryDelayLoadAllImports(LPCSTR szDll)
{
    __try
    {
        HRESULT hr = __HrLoadAllImportsForDll(szDll);
        if (FAILED(hr))
        {
            // printf_s("Failed to delay load functions from %s\n", szDll);
            return false;
        }
    }
    __except (CheckDelayException(GetExceptionCode()))
    {
        // printf_s("Delay load exception for %s\n", szDll);
        return false;
    }
    // printf_s("Delay load completed for %s\n", szDll);
    return true;
}

Результат можно использовать TryDelayLoadAllImports для управления вызовом функций импорта или нет.

Обработка ошибок и предупреждений

Если программа использует библиотеки DLL с задержкой, она должна надежно обрабатывать ошибки. Сбои, возникающие во время выполнения программы, приводят к необработанным исключениям. Дополнительные сведения об обработке и уведомлении о задержке загрузки DLL см. в статье об обработке ошибок и уведомлениях.

Дамп импорта для отложенной загрузки

Импорт, загруженный с задержкой, можно дампать с помощью DUMPBIN /IMPORTS. Эти импорты отображаются немного иначе, чем стандартные импорты. Они разделены на свой собственный раздел /imports списка и явно помечены как импорт с задержкой. Если в изображении есть сведения о выгрузке, это отмечается. Если есть сведения о привязке, время и метка даты целевой библиотеки DLL отмечается вместе с привязанными адресами импорта.

Ограничения библиотек DLL для задержки загрузки

Существует несколько ограничений на задержку загрузки импорта БИБЛИОТЕК DLL.

  • Не поддерживается импорт данных. Решение заключается в том, чтобы явно обрабатывать импорт данных самостоятельно с помощью (или с помощью LoadLibraryGetModuleHandle после того, как вы знаете, что вспомогательный элемент задержки загрузки загрузил библиотеку DLL) и GetProcAddress.

  • Задержка загрузки Kernel32.dll не поддерживается. Эта библиотека DLL должна быть загружена для выполнения вспомогательных подпрограмм задержки загрузки.

  • Привязка переадресированных точек входа не поддерживается.

  • Процесс может иметь другое поведение, если библиотека DLL загружается с задержкой, а не загружается при запуске. Его можно увидеть, есть ли инициализации для каждого процесса, которые происходят в точке входа загруженной библиотеки DLL с задержкой. Другие случаи включают статический TLS (локальное хранилище потока), объявленный с помощью__declspec(thread), который не обрабатывается при загрузке библиотеки DLL.LoadLibrary Тем не менее, как в статических библиотеках DLL, так и в библиотеках DLL, загружаемых с задержкой, доступна для использования динамическая память TLS, реализуемая с помощью функций TlsAlloc, TlsFree, TlsGetValue и TlsSetValue.

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

  • В настоящее время невозможно отложить загрузку только определенных процедур из библиотеки DLL при использовании нормального механизма импорта.

  • Пользовательские соглашения о вызовах (например, использование кодов условий в архитектурах x86) не поддерживаются. Кроме того, регистры с плавающей запятой не сохраняются на любой платформе. Убедитесь, что пользовательские вспомогательные подпрограммы или подпрограммы перехватчика используют типы с плавающей запятой: подпрограммы должны сохранять и восстанавливать полное состояние с плавающей запятой на компьютерах, использующих соглашения о регистрации вызовов с параметрами с плавающей запятой. Будьте осторожны с задержкой загрузки библиотеки DLL CRT, особенно при вызове функций CRT, которые принимают параметры с плавающей запятой в стеке числовых обработчиков данных (NDP) в функции справки.

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

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

См. также

Создание библиотек DLL на C и C++ в Visual Studio
Справочник по компоновщику MSVC