2016-01-19 25 views
6

有一個DLL,由主(桌面)應用程序通過Windows.LoadLibrary動態加載。這是因爲有很多類似的DLL,只有少數幾個需要在運行時加載。所以靜態鏈接不是一種選擇。Delphi DLL是否預定用於加載程序鎖定?

問題是,加載其中一個DLL時,每隔一段時間主應用程序就會掛起。請注意,這個問題很可能會發生在每一個人身上。可能是因爲他們有很多共同的代碼庫。

問題似乎是一個裝載機鎖see this SO answer on what it is)。我找到了一段通用代碼,它在啓動時使用的begin...end部分library -unit(即project.dpr)中的所有DLL都被使用,其中GetModuleHandleGetProcAddress被使用。

我發現,這是一個完全用的DLL DONT,作爲begin...end -section DLL的項目文件實際上是在圖書館的DllMain功能及調用這些函數可導致死鎖(命名爲裝載機鎖) 。我讀到Microsoft Best Practice Guide

因此,我重建我的代碼,這些電話稍後調用,當Windows.LoadLibrary的呼叫已完成。

不幸的是,懸掛問題依然存在。 :-(

我運行了調試器,然後逐步完成了每一行初始化操作,甚至在執行一行代碼之前調用了這些初始化代碼。我確定,很多第三方代碼違反了指南的做法,什麼不能在DLL初始化代碼做:

  • TMS組件包
  • JEDI組件庫
  • OmniThreadLibrary
  • Indy組件

以上所有動態加載initialization中的其他DLL,或通過GetProcAddress請求過程指針。我認爲這些調用導致掛載當我的DLL加載。

難道只有少數德爾菲開發人員知道initialization的危險嗎?我能做些什麼呢?

+3

根據鏈接到的文檔,在DLL初始化期間,可以安全地使用'GetModuleHandle()'和'GetProcAddress()'。 Indy在運行時動態加載多個DLL,但是它們中的任何一個都不應該加載到任何「初始化」節中,它們在實際調用DLL函數時會根據需要動態加載。但是我剛剛發現了一個正在加載到'initialization'部分的DLL(zlib),所以我現在修復了這個問題。 –

+0

請參閱api函數文檔中的「庫」部分。 GetModuleHandle和GetProcAddress是內核函數。 –

+0

因此,這[網站](https://blog.barthe.ph/2009/07/30/no-stdlib-in-dllmai/)錯誤關於'GetModuleHandle'?它說*「這個」加載程序鎖定「在任何時候加載一個程序庫時被使用,而且當使用像GetModuleHandle()或GetModuleFileName()這樣的函數時。」* –

回答

6

這是一個常見的問題,我不認爲是特別針對Delphi程序員。如果您的代碼在初始化部分中調用了LoadLibrary,或者在最終化部分中確實調用了FreeLibrary,那麼該代碼對於在庫中使用是不安全的。

請注意,我並不熟悉您提及的所有庫,並且完全沒有確認它們全都具有initialization部分代碼,這些代碼在庫中不安全。我認爲這是你需要確認的東西 - 我想堅持這個答案中的概念,而不是對具體的Delphi庫進行評論。我想說GetModuleHandleGetProcAddressDllMain罰款。我說,因爲你具體提到GetProcAddress。例如,通過調用GetModuleHandle來獲取模塊句柄,然後通過調用GetProcAddress來獲得函數地址,這是絕對沒問題的。所以如果一些嫌疑人的圖書館這樣做,並且不打電話給LoadLibrary,那麼他們可能會沒事的。

不管怎樣,受上述限制性條款,你需要做出安排,將來自DllMain被稱爲是違反微軟訂下的規則的任何代碼被稱爲在一個安全的時間,而不是,而不是從DllMain。不幸的是,這些規則充其量只是模糊不清Microsoft say the following in the DllMain documentation

入口點函數應該只執行簡單初始化或終止任務 。它不能調用LoadLibrary或LoadLibraryEx函數(或調用這些函數的函數),因爲這可能會在DLL加載順序中創建依賴項循環。這可能會導致在系統執行其初始化代碼之前使用了一個 DLL。 類似地,入口點函數不能在進程 終止期間調用FreeLibrary 函數(或調用FreeLibrary的函數),因爲這會導致DLL在系統執行其終止代碼之後被使用。

因爲Kernel32.dll中是有保證的過程中地址被加載時,入口點函數被調用 空間,調用函數中 Kernel32.dll中不會導致在DLL中使用其 初始化代碼已經前執行。因此,入口點 函數可以調用Kernel32.dll中不會加載其他 DLL的函數。例如,DllMain可以創建同步對象,例如 關鍵部分和互斥鎖,並使用TLS。不幸的是,在Kernel32.dll中不存在安全功能的完整列表 。

最後一段文字給你一點點指導。要相信,你的圖書館將是穩健的,你需要遵循以下方針做一些事情:

  1. 安排,任何單位,其源代碼,可以控制與中央註冊表中的初始化和結束程序寄存器的每個初始化部分。
  2. 在可執行項目中,您在註冊時調用初始化過程,並在程序終止時按相反順序調用完成過程。
  3. 在庫項目中,您推遲調用這些初始化和最終化過程。從DLL的使用者可以調用的DLL中導出一對函數,以請求調用這些初始化和終結過程。

這是我用我的圖書館採取的方法,它已經很好地服務了我多年。

這種方法涉及相當多的工作,並且存在修改第三方庫的缺點。但是,如果這些庫在交付使用時無法正常工作,您還有什麼選擇?

也許在較慢的時間內,您可以聯繫您認爲與庫中使用不兼容的任何庫的開發人員。試着說服他們改變他們的代碼,使其與庫中的使用兼容。從Remy對您的問題的評論中可以看到,圖書館開發人員完全有可能不知道這個問題,並且非常願意進行更改。

0

如果您在程序中進行動態加載,那麼windows DLL不會導致加載程序鎖定,因爲它們在程序中的第一個代碼有機會執行時已經加載。因此只能在您的庫之間引起加載程序鎖定。在這種情況下,您將必須確定正確的加載順序。如果你有一些文件,然後搜索它。

如果您的所有庫都是Delphi/C++ Builder庫,並且都在相同的RAD Studio版本中編譯,那麼我建議您爲它們啓用運行時包。這將減少代碼重複並有機會獲得鎖定,因爲像Application這樣的singelton對象的兩個實例可以同時工作。或者甚至更好 - 將你的庫轉換爲包。這將消除任何鎖的機會。俄羅斯博客

+0

這根本不準確。某些系統DLL在進程啓動時加載。但絕不是全部。其他人可以稍後加載。和加載順序不相關。您無法控制隱式加載庫的加載順序。轉換爲運行時包幾乎沒有幫助。它們也只是DLL。誰說你甚至可以做到這一點。如果你不控制所有的DLL,會怎麼樣? –

+0

如果您不相信將Delphi Dll轉換爲包可以100%消除它們之間的鎖,那麼您應該更好地瞭解它們是如何實現的。 – Torbins

+0

對於Delphi和非Delphi庫混合使用的情況,可以將需要的非Delphi庫靜態導入到主exe文件中。或者甚至更好:將所有Delphi代碼重寫爲C++並靜態鏈接所有內容。 – Torbins

1

理念:http://www.gunsmoker.ru/

您可以創建中的DLL德爾福不做任何事情在它的DllMain。爲了做到這一點,你應該像下面創建新的包:

package Plugin1; 

//... 
{$E dll} 

contains 
    InitHook, 
    //... 
    ; 

end. 

而且InitHook:

unit InitHook; 

interface 

implementation 

function GetProcAddress(hModule: HMODULE; lpProcName: PAnsiChar): Pointer; stdcall; external 'kernel32.dll' name 'GetProcAddress'; 

procedure Done; stdcall; 
type 
    TPackageUnload = procedure; 
var 
    PackageUnload: TPackageUnload; 
begin 
    @PackageUnload := GetProcAddress(HInstance, 'Finalize'); //Do not localize 
    PackageUnload; 
end; 

procedure Init; stdcall; 
type 
    TPackageLoad = procedure; 
var 
    PackageLoad: TPackageLoad; 
begin 
    @PackageLoad := GetProcAddress(HInstance, 'Initialize'); //Do not localize 
    PackageLoad; 
end; 

exports 
    Init, 
    Done; 
end. 

現在你可以把這個包裏面,你希望把裏面的Dll的任何代碼。但是你必須在調用這個dll的任何其他函數之前調用Init,並在卸載它之前調用Done。

Initialize和Finalize是程序,編譯器自動在包中創建。這些程序執行包中所有單元的所有初始化和最終化部分。