先看一下使用Delphi開發DLL時如何使用MAIN函數,安全
一般狀況下並不會使用到DLL的MAIN函數,由於delphi的框架已經把Main函數隱藏起來多線程
而工程函數的 begin end 默認就是MAIN函數的DLL_PROCESS_ATTACH事件的處理代碼,如須要完整的處理其餘事件,框架
如 DLL_PROCESS_DETACH,DLL_THREAD_ATTACH, DLL_THREAD_DETACH,可在工程文件中作以下處理:函數
procedure DLLEntryPoint(Reason:DWord); begin case Reason of DLL_PROCESS_ATTACH: StartMyThreadsAndWaitBegin(); // 建立並等待線程開始,這樣會致使卡死 DLL_PROCESS_DETACH: StopMyThreadsAndWaitEnd(); // 中止並等待線程結束(或直接結束進程),這樣會致使卡死 DLL_THREAD_ATTACH:; DLL_THREAD_DETACH:; end; end; begin DllProc := @DLLEntryPoint; DLLEntryPoint(DLL_PROCESS_ATTACH); end.
其中 DllProc 是SysInit中的全局變量,可簡單理解爲保存DLL Entry Point入口函數的地址(實際上RTL內部還有InitLib 和StartLib函數,由編譯器自動處理)。spa
以上都是題外話,本文主要說明在DLL入口函數裏面建立和退出線程爲何卡死和如何解決的問題。線程
1)在 DLL_PROCESS_ATTACH 事件中 建立線程 出現卡死的問題進程
一般狀況下在這事件中僅僅是建立並喚醒線程,是不會卡死的,但若是同時有等待線程正式執行的代碼,則會卡死,由於在該事件中,任何啓動的線程都會因爲LdrLoadDll中的LdrpLoaderLock 進入鎖定狀態而處於等待,沒法進入線程函數,因此也就永遠沒法檢測到正式執行的機會。事件
LdrpLoaderLock是系統的PE Loader的一個重要鎖,保證系統資源的安全,而DLL 入口函數是在PE Loader 結束前執行的,LdrInitializeThunk等函數處理PE 映像 到內存中的過程當中,LdrpLoaderLock是處於鎖定狀態的。內存
因此解決辦法就是 在 DLL_PROCESS_ATTACH 事件中,僅建立並喚醒線程便可(此時即便是喚醒了,線程也是處理等待狀態),線程函數會在DLL_PROCESS_ATTACH事件結束後才正式執行(實際上若是是經過LoadLibrary加載DLL,則會在LoadLibrary結束先後的某一時刻正式執行)。資源
2)在DLL_PROCESS_DETACH中結束線程出現卡死的問題
一樣的緣由,該事件是調用LdrUnloadDll中執行的,LdrpLoaderLock仍然是鎖定狀態的,而結束線程最終會調用LdrShutdownThread,均會釋放PE Loader所維護的系統內部的共同資源(包括PEB 和TEB等模塊信息和線程TLS數據等),此類共同資源恰好都是使用LdrpLoaderLock進行同步,因此在DLL_PROCESS_DETACH中調用ExitThread->LdrShutdownThread,必然致使卡死。
另外有一個特殊的現象,就是DLL_PROCESS_DETACH事件中,線程處於掛起狀態,這是由於系統分配線程執行時間片的過程當中因爲PE Loader有資源處於鎖定而致使線程沒法進行下一個時間片,最終表現爲線程函數處於假死狀態,此狀態基本上等同於線程的掛起(suspend)狀態。
解決辦法一樣是避免在 DLL_PROCESS_DETACH事件中結束線程,那麼咱們能夠在該事件中,建立並喚醒另一個線程,在該新的線程裏,結束須要結束的線程,並在完成後結束自身便可。惟一須要注意的是,一旦DLL_PROCESS_DETACH結束,內存中與DLL相關的PE映像資源可能會被釋放掉,因此在後續的操做中儘可能不要再對原來的數據進行操做,不然容易致使內存溢出(但其實釋放與否是由內核決定的,也許未來通過某一個版本的補丁後,相關資源仍然會保留在內存能夠使用)。
提醒: 標準的作法仍是建議遵循MS的規則,不要在DLL入口函數中作線程相關的建立和釋放操做。
整體上代碼以下:
procedure DLLEntryPoint(Reason:DWord); begin case Reason of DLL_PROCESS_ATTACH: TThread.CreateAnonymousThread(procedure begin StartMyThreadsAndWaitBegin(); end).Start; DLL_PROCESS_DETACH: TThread.CreateAnonymousThread(procedure begin StopMyThreadsAndWaitEnd(); end).Start; DLL_THREAD_ATTACH:; DLL_THREAD_DETACH:; end; end; begin DllProc := @DLLEntryPoint; DLLEntryPoint(DLL_PROCESS_ATTACH); end.
注: 此問題是屬於系統多線程處理的問題,或者說是屬於Windows API的使用方法問題,使用其餘VB VC等開發的人員也能夠參考此解決方法。