發(fā)布時(shí)間:2011-08-29 共3頁(yè)
//創(chuàng)建DLL內(nèi)的線程對(duì)象
g_thread_handle = ::CreateThread(NULL,0,
InSideDll_ThreadProc,(LPVOID)0,0, &( g_thread_id) ) ;
// 禁止線程庫(kù)調(diào)用,
DisableThreadLibraryCalls((HINSTANCE)hModule);
}
break;
case DLL_PROCESS_DETACH:
// DLL正在從進(jìn)程地址空間中卸載
{
// 通知內(nèi)部的線程g_thread_handle 退出
::SetEvent(g_hEvent);
// 等待內(nèi)部的線程g_thread_handle 退出
::WaitForSingleObject(g_thread_handle, INFINITE ) ;
// 清除資源
::CloseHandle(g_thread_handle);
g_thread_id = 0 ;
g_thread_handle = NULL ;
::CloseHandle(g_hEvent);
g_hEvent=NULL;
}
break;
}
return TRUE;
}
//----------------------end ------------
上述代碼的流程是這樣的:
(1)裝載DLL時(shí),創(chuàng)建一個(gè) DLL內(nèi)部的線程g_thread_handle及事件對(duì)象g_hEvent,且線程g_thread_handle在事件對(duì)象g_hEvent上等待。
(2)卸載DLL時(shí),通過(guò)SetEvent(g_hEvent)通知線程g_thread_handle退出,隨即調(diào)用 WaitForSingleObject函數(shù)等待線程g_thread_handle終止運(yùn)行。如果線程g_thread_handle終止運(yùn)行,則執(zhí)行清除工作。
但是如果對(duì)這樣的程序進(jìn)行調(diào)試,就會(huì)發(fā)現(xiàn)程序在退出時(shí)該DllMain沒(méi)有退出,等待了很長(zhǎng)時(shí)間也沒(méi)有退出。
查看了一下線程Call Stack窗口,注意到程序正在等待DllMain內(nèi)部的線程g_thread_handle的退出。盡管線程g_thread_handle的線程函數(shù)已經(jīng)返回了,但是整個(gè)g_thread_handle線程走到了操作系統(tǒng)的ntdll.dll中并沒(méi)有完全終止,從而導(dǎo)致整個(gè)DLL不能順利釋放。
線程g_thread_handle為什么沒(méi)有完全退出呢?
原來(lái),線程函數(shù)返回時(shí),系統(tǒng)并不立即將它撤消。相反,系統(tǒng)要取出這個(gè)即將被撤消的線程,讓它調(diào)用已經(jīng)映射的DLL的所有帶有 DLL_THREAD_DETACH值的、且沒(méi)有調(diào)用DisableThreadLibraryCalls函數(shù)的DllMain函數(shù)。 DLL_THREAD_DETACH通知告訴所有的DLL執(zhí)行每個(gè)線程的清除操作,例如,DLL版本的C/C++運(yùn)行期庫(kù)能夠釋放它用于管理多線程應(yīng)用程序的數(shù)據(jù)塊。DisableThreadLibraryCalls函數(shù)告訴系統(tǒng)說(shuō),特定的DLL的DllMain函數(shù)不用接收DLL_THREAD_ATTACH和DLL_THREAD_DETACH通知。
但是,系統(tǒng)是順序調(diào)用DLL的DllMain函數(shù)的。
當(dāng)線程函數(shù)返回時(shí),系統(tǒng)檢查進(jìn)程中是否存在沒(méi)有調(diào)用DisableThreadLibraryCalls函數(shù)的DllMain函數(shù),如果存在,系統(tǒng)就以進(jìn)程的互斥對(duì)象的句柄作為第一個(gè)參數(shù),在線程內(nèi)部調(diào)用WaitForSingleObject函數(shù)。一旦這個(gè)將要終止運(yùn)行的線程擁有該進(jìn)程互斥對(duì)象,系統(tǒng)就讓該線程用DLL_THREAD_DETACH的值依次調(diào)用每個(gè)沒(méi)有調(diào)用DisableThreadLibraryCalls函數(shù)的DLL的 DllMain函數(shù)。此后,系統(tǒng)才釋放對(duì)進(jìn)程互斥對(duì)象的所有權(quán)。
在本例所述的應(yīng)用程序中,進(jìn)程的退出引起操作系統(tǒng)獲取進(jìn)程互斥對(duì)象使操作系統(tǒng)可以為DLL_PROCESS_DETACH通知調(diào)用 DllMain()。該DLL的DllMain()函數(shù)通知線程g_thread_handle終止運(yùn)行。無(wú)論何時(shí)當(dāng)進(jìn)程終止一個(gè)線程時(shí),操作系統(tǒng)將獲取進(jìn)程互斥對(duì)象,以便于它可以為DLL_THREAD_DETACH通知調(diào)用每個(gè)加載的、沒(méi)有調(diào)用DisableThreadLibraryCalls函數(shù)的DLL的DllMain函數(shù)。在這個(gè)特定的程序中,線程g_thread_handle當(dāng)線程函數(shù)返回后就阻塞了,因?yàn)镃MySingleton的 DllMain()所處的線程還保持著進(jìn)程互斥對(duì)象。不幸的是,DllMain所處的線程然后調(diào)用WaitForSingleObject確認(rèn) g_thread_handle線程是否完全終止。因?yàn)間_thread_handle線程被阻塞在進(jìn)程互斥對(duì)象上,這個(gè)進(jìn)程互斥對(duì)象還被DllMain 線程所持有, DllMain線程要等待g_thread_handle線程從而也被阻塞,結(jié)果就導(dǎo)致了死鎖。如下圖所示:
注意,從圖2可以看出,如果當(dāng)前進(jìn)程中的所有 DLL都調(diào)用了DisableThreadLibraryCalls函數(shù),那么上述代碼中的DLL也能正常退出。曾經(jīng)寫(xiě)過(guò)一個(gè)程序,除了加載一個(gè)這樣有問(wèn)題的DLL沒(méi)有加載其他DLL(系統(tǒng)的DLL除外),程序能夠正常退出。
3、結(jié)論
很顯然的一個(gè)教訓(xùn)就是在DllMain內(nèi)部應(yīng)該避免任何Wait*調(diào)用。但是進(jìn)程互斥對(duì)象的問(wèn)題不僅僅限于Wait*函數(shù)。操作系統(tǒng)在 CreateProcess、GetModuleFileName、GetProcAddress、wglMakeCurrent、 LoadLibrary和FreeLibrary等函數(shù)中在后臺(tái)獲取進(jìn)程互斥對(duì)象,因此在DllMain中不應(yīng)該調(diào)用任何這些函數(shù)。因?yàn)镈llMain獲取進(jìn)程互斥對(duì)象,所以一次只能有一個(gè)線程執(zhí)行DllMain。
ATL singleton的 FinalConstruct函數(shù)和FinalRelease函數(shù)分別是DllMain在響應(yīng)DLL_PROCESS_ATTACH和DLL_PROCESS_DETACH時(shí)被調(diào)用的,所以也要同樣注意本文所述的問(wèn)題。
編輯特別推薦: