Delphi 如何解決在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函數,由編譯器自動處理)。

以上都是題外話,本文主要說明在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等開發的人員也可以參考此解決方法。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM