Поддержка компоновщика для библиотек 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
в каталоге MSVCinclude
. Дополнительные сведения см. в разделе "Общие сведения о вспомогательной функции задержки загрузки".Параметр
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.
Не поддерживается импорт данных. Решение заключается в том, чтобы явно обрабатывать импорт данных самостоятельно с помощью (или с помощью
LoadLibrary
GetModuleHandle
после того, как вы знаете, что вспомогательный элемент задержки загрузки загрузил библиотеку 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