發布時間:2011-08-29 共3頁
// 清除資源
::CloseHandle(g_thread_handle);
g_thread_id = 0 ;
g_thread_handle = NULL ;
::CloseHandle(g_hEvent);
g_hEvent=NULL;
}
break;
case DLL_PROCESS_DETACH:
// DLL正在從進程地址空間中卸載
break;
}
return TRUE;
}
//----------------------end ------------
如果對這樣的程序進行調試,通過Call Stack窗口可以看到該程序正在等待DllMain內部的線程處理,而Output窗口中也沒有打印出“---- operations.---- ”語句。可見線程函數InSideDll_ThreadProc根本就沒有得到運行的機會。
結合DllMain的順序調用規則,答案就很簡單了。在程序運行過程中,第一個線程對LoadLibrary的調用引起操作系統獲取進程互斥對象并以DLL_PROCESS_ATTACH值調用該DLL的DllMain。該DLL的DllMain函數產生第二個線程。無論何時當進程產生一個新線程時,操作系統將獲取進程互斥對象,以便于它可以為DLL_THREAD_ATTACH值調用每個加載的DLL的DllMain函數。在這個特定的程序中,第二個線程阻塞,因為第一個線程還保持著進程互斥對象。不幸的是,第一個線程然后調用WaitForSingleObject確認第二個線程能夠正確地完成一些操作。因為第二個線程被阻塞在進程互斥對象上,這個進程互斥對象還被第一個線程所持有,而第一個線程要等待第二個線程從而也被阻塞,結果就導致了死鎖。如下圖所示。
另外,DisableThreadLibraryCalls函數并不能解除這種死鎖,相關原因在《Windows核心編程》一書中有更詳盡的描述,這里就不再贅述了。
2.2、卸載DLL時內部線程為什么沒有完全退出
估計很多人都知道裝載DLL過程中的多線程死鎖是因為DllMain的順序調用規則,但是很少人了解卸載DLL過程中的多線程死鎖也是由于同樣的原因。例如,如果一個DLL的DllMain的代碼寫成下面的形式,且進程中有至少一個DLL的DllMain沒有調用DisableThreadLibraryCalls函數的話,那么卸載該DLL過程中就會因為 DllMain的順序操作特性帶來DLL內部線程沒有完全退出的錯誤。
//----------------------start ------------
HANDLE g_thread_handle =NULL; // 該DLL內部線程的句柄
DWORD g_thread_id =0; // 該DLL內部線程的ID
HANDLE g_hEvent=NULL;// 應答事件的句柄
DWORD WINAPI InSideDll_ThreadProc( LPVOID p )
{
while(1){
// 收到通知就退出線程函數
DWORD ret = ::WaitForSingleObject( g_hEvent, INFINITE );
if(WAIT_TIMEOUT = =ret|| WAIT_OBJECT_0 = =ret) break;
}
return true ;
}
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
//線程正在映射到進程地址空間中
{
// 創建DLL內的線程使用的事件對象
g_hEvent = ::CreateEvent( NULL, FALSE, FALSE, _T("hello11" ));