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