// Содержание
Привет, коллеги! ? Вы профессиональные C++ разработчики, пишете идеальный код, но внезапно ваше приложение зависает при загрузке DLL. Виновник — deadlock в DllMain. Почему это происходит и как это исправить? Давайте разберёмся!
? Почему DllMain — зона повышенной опасности?
При загрузке или выгрузке DLL система входит в критическую секцию загрузчика (Loader Lock). Ваша функция DllMain выполняется под этой блокировкой. Если вы попытаетесь сделать что-то, что требует другой блокировки (например, создание потока), — deadlock неизбежен.
? Типичные причины deadlock:
- Создание/остановка потоков в
DllMain. - Вызов функций, которые используют синхронизацию (мьютексы, критические секции).
- Обращение к другим DLL, которые могут зависеть от загрузчика.
? Пример-убийца: Deadlock в действии
#include <Windows.h>
#include <thread>
BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID lpReserved) {
switch (reason) {
case DLL_PROCESS_ATTACH:
// Опасность! Создание потока под Loader Lock.
std::thread killer_thread([]() {
// Поток пытается загрузить другую DLL -> deadlock!
LoadLibraryA("Another.dll");
});
killer_thread.join(); // Вечное ожидание...
break;
}
return TRUE;
}Что происходит:
- Главный поток держит Loader Lock.
- Новый поток пытается вызвать
LoadLibrary, которая требует той же блокировки. - Результат: оба потока ждут друг друга → deadlock.
?️ 5 правил выживания в DllMain
- Минимум кода:
ВDllMain— только инициализация без зависимостей. Никаких сложных операций! - Запрещено создавать/останавливать потоки:
Используйте отложенную инициализацию. Например:
static bool is_initialized = false;
if (reason == DLL_PROCESS_ATTACH && !is_initialized) {
// Запланировать инициализацию, но не выполнять здесь!
std::call_once(init_flag, []() { /* безопасная инициализация */ });
}- Не вызывайте другие DLL:
Особенно если те могут зависеть от загрузчика. Используйте явную загрузку (LoadLibrary/GetProcAddress) после инициализации. - Избегайте синхронизации:
Мьютексы, критические секции,std::sync— под запретом. Если нужно — используйте атомарные операции. - Разделяйте инициализацию:
Вынесите код в отдельную функцию, которую вызовет приложение после загрузки:
// DllMain
BOOL APIENTRY DllMain(HMODULE, DWORD, LPVOID) { return TRUE; }
// Явная инициализация
extern "C" __declspec(dllexport) void SafeInit() {
// Здесь можно всё: потоки, синхронизация и т.д.
}? Шаблон безопасной DLL
#include <Windows.h>
// Флаг для отложенной инициализации
static std::once_flag g_init_flag;
BOOL APIENTRY DllMain(HMODULE, DWORD reason, LPVOID) {
if (reason == DLL_PROCESS_ATTACH) {
// Максимум что можно - запланировать инициализацию
std::call_once(g_init_flag, []() { /* безопасный код */ });
}
return TRUE;
}
// Явный вызов инициализации
extern "C" __declspec(dllexport) void InitDLL() {
std::call_once(g_init_flag, []() { /* полноценная инициализация */ });
}Итог: Ваши действия
- Никогда не делайте в
DllMainтого, что требует времени. - Документируйте для пользователей DLL: «Вызовите
InitDLL()после загрузки!». - Тестируйте под нагрузкой: запускайте параллельную загрузку DLL в 10+ потоков.
? Помните:
DllMain— не место для творчества. Чем проще, тем надежнее!
Удачи в борьбе с deadlock! ?
Ваш код будет безупречным, а приложения — стабильными.
// КОММЕНТАРИИ (0)