如何解決在DLL的入口函數中建立或結束線程時卡死

先看一下使用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等開發的人員也能夠參考此解決方法。

相關文章
相關標籤/搜索