Tuesday, August 17, 2010

DLL_PROCESS_DETACH и С++

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

Функция ExitProcess, в свою очередь, известна тем, что сначала останавливает все потоки, кроме вызывающего (используя NtTerminateProcess), а потом вызывает EntryPoint для всех dll с флагом DLL_PROCESS_DETACH. В С++ обычно точка входа называется DllMainCRTStartup. DllMainCRTStartup вызывает функцию DllMain и после нее все деструкторы глобальных объектов, расположенных в DLL.

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

Поэтому, популярный паттерн использования DLL_PROCESS_DETACH выглядит так:
BOOL WINAPI DllMain(
IN HINSTANCE hinstDll,
IN DWORDfdwReason,
LPVOID lpvReserved
)
{
....

case DLL_PROCESS_DETACH:

if( lpvReserved == NULL )
{
// FreeLibrary is called and it is safe to free resources
// All critical sections and other primitives should work fine
FreeResources1();
FreeResources2();
...
FreeResourcesN();
}
// else { ExitProcess is called, process is terminating, so do nothing }
break;
}
_Winnie C++ Colorizer

Параметр lpvReserved описан в MSDN следующим образом:
If fdwReason is DLL_PROCESS_DETACH, lpvReserved is NULL if FreeLibrary has been called or the DLL load failed and non-NULL if the process is terminating.
(с) MSDN


В случае использования С++, код нашей идеальной Dll должен выглядеть так:
static std::auto_ptr<CMyDll> g_myDll;

BOOL WINAPI DllMain(
IN HINSTANCE hinstDll,
IN DWORD fdwReason,
LPVOID lpvReserved
)
{
....

case DLL_PROCESS_DETACH:

if( lpvReserved == NULL )
{
g_myDll.reset(0);
}
g_myDll.release();
break;
}
_Winnie C++ Colorizer

В приведенном коде нет особых проблем, но нельзя упускать тот факт, что наша идеальная dll может подключать и, как правило, подключает множество статических библиотек. Которые вполне могут содержать глобальные переменные-объекты.

Выводы крайне тривиальны: использование глобальных переменных-объектов - есть зло, особенно в статических библиотеках. Но если вы уже разрабатываете статическую библиотеку на С++ с глобальными переменными внутри, стоит предоставить функцию для их корректного фатального освобождения, с семантикой, аналогичной g_myDll.release().

No comments: