2015-12-21 52 views
1

我有一個Delphi DLL,需要從我的主UI應用程序或工作線程調用。動態初始化並從TThread按需調用LoadLibrary一次

我不想在每次調用DLL時調用LoadLibrary/FreeLibrary。但是,我也不想在我的應用程序初始化部分加載它。因爲我可能在應用程序的生命週期中根本不使用DLL。

所以我需要的是第一個調用者(線程或主UI)來初始化和加載DLL。 該DLL將在最終化部分中卸載。我意識到我需要一些同步。所以我使用了關鍵部分,但我似乎無法使其工作。

只有一個線程應該嘗試並加載DLL。如果失敗,其他線程不應該嘗試一次又一次加載DLL。

如預期的那樣,同步是不工作
有人可以建議爲什麼嗎?

MCVE:

program Project1; 
{$APPTYPE CONSOLE} 
uses 
    Windows, 
    SysUtils, 
    Classes; 

const 
    MyDLL = 'MyDLL.dll'; 

type 
    TDLLProcessProc = function(A: Integer): Integer; stdcall; 

var 
    DLLProc: TDLLProcessProc = nil; 
    DLLModule: HMODULE = 0; 
    DLLInitialized: Boolean = False; 
    DLLInitialized_OK: Boolean = False; 
    CS: TRTLCriticalSection; 

procedure InitDLLByFirstCall; 
begin 
    if DLLModule = 0 then 
    begin 
    if DLLInitialized then Exit; 
    EnterCriticalSection(CS); 
    try 
     if DLLInitialized then Exit; 
     DLLInitialized := True; 
     DLLModule := LoadLibrary(MyDLL); 
     if DLLModule = 0 then RaiseLastWin32Error; 
     DLLProc := GetProcAddress(DLLModule, 'Process'); 
     if @DLLProc = nil then RaiseLastWin32Error; 
     DLLInitialized_OK := True; 
    finally 
     LeaveCriticalSection(CS); 
    end; 
    end; 
end; 

function DLLProcess(A: Integer): Integer; 
begin 
    InitDLLByFirstCall; 
    if not DLLInitialized_OK then 
    raise Exception.Create('DLL was not initialized OK'); 
    Result := DLLProc(A); 
end; 

type 
    TDLLThread = class(TThread) 
    private 
    FNum: Integer; 
    public 
    constructor Create(CreateSuspended: Boolean; ANum: Integer); 
    procedure Execute; override; 
    end; 

constructor TDLLThread.Create(CreateSuspended: Boolean; ANum: Integer); 
begin 
    FreeOnTerminate := True; 
    FNum := ANum; 
    inherited Create(CreateSuspended); 
end; 

procedure TDLLThread.Execute; 
var 
    RetValue: Integer; 
begin 
    try 
    RetValue := DLLProcess(FNum); 
    Sleep(0); 
    Writeln('TDLLThread Result=> ' + IntToStr(RetValue)); 
    except 
    on E: Exception do 
    begin 
     Writeln('TDLLThread Error: ' + E.Message); 
    end; 
    end; 
end; 

var 
    I: Integer; 

begin 
    InitializeCriticalSection(CS); 
    try 
    // First 10 thread always fail! 
    for I := 1 to 10 do 
     TDLLThread.Create(False, I); 
    Readln; 

    for I := 1 to 10 do 
     TDLLThread.Create(False, I); 
    Readln; 
    finally 
    DeleteCriticalSection(CS); 
    end; 
end. 

DLL:

library MyDLL; 

uses 
    Windows; 

{$R *.res}   

function Process(A: Integer): Integer; stdcall; 
begin 
    Result := A; 
end; 

exports 
    Process; 

begin 
    IsMultiThread := True; 
end. 
+0

「未按預期工作」是什麼意思?預期的行爲是什麼?觀察到的行爲是什麼?他們有什麼不同? –

+2

請勿檢查「DLLModule = 0」。刪除'DLLInitialized_OK'。只有初始化完成後才設置'DLLInitialized'。 -1沒有真正嘗試。 – mghie

+0

CS可以在線程執行之前銷燬。 –

回答

4

您需要修改代碼,以便在所有初始化完成後才設置在InitDLLByFirstCall開頭檢查的條件變量。 DLL句柄因此是一個不好的選擇。

其次,您需要在關鍵部分的外部和內部使用相同的條件變量 - 如果您使用DLLInitialized,那麼DLLInitialized_OKDLLModule都不是真的有用。

爲了讓事情更容易推理,你應該嘗試擺脫最少數量的變量。像下面這樣的東西應該工作:

var 
    DLLProc: TDLLProcessProc = nil; 
    DLLInitialized: Boolean = False; 
    CS: TRTLCriticalSection; 

procedure InitDLLByFirstCall; 
var 
    DLLModule: HMODULE; 
begin 
    if DLLInitialized then 
    Exit; 

    EnterCriticalSection(CS); 
    try 
    if not DLLInitialized then 
    try 
     DLLModule := LoadLibrary(MyDLL); 
     Win32Check(DLLModule <> 0); 

     DLLProc := GetProcAddress(DLLModule, 'Process'); 
     Win32Check(Assigned(DLLProc)); 
    finally 
     DLLInitialized := True; 
    end; 
    finally 
    LeaveCriticalSection(CS); 
    end; 
end; 

function DLLProcess(A: Integer): Integer; 
begin 
    InitDLLByFirstCall; 
    if @DLLProc = nil then 
    raise Exception.Create('DLL was not initialized OK'); 
    Result := DLLProc(A); 
end; 

如果你不想檢查函數地址的DLLProcess裏面,那麼你也可以使用爲DLLInitialized變量的整數或枚舉,具有不同的值未初始化,失敗成功

+0

我可以保存DLLModule全局,就像我以後用FreeLibrary一樣嗎? – zig

+1

@zig:你可以,但如果模塊保持加載狀態直到程序關閉,則沒有實際的用處。無論如何,Windows會在進程終止後清理乾淨。 – mghie

+0

謝謝@mghie,我非常感謝你的努力。 – zig

1

你有你的雙重檢查鎖定實現不正確。在分配給DLLProc之前,您分配給DLLModule。因此DLLModule可以非零,而DLLProc仍然爲空。

在所有初始化完成後,必須修改在鎖外測試的變量。

的模式是這樣的:

if not Initialised then begin 
    Lock.Enter; 
    if not Initialised then begin 
    // Do initialisation 
    Initialised := True; // after initialisation complete 
    end; 
    Lock.Leave; 
end; 

記住,雙重檢查鎖定,因爲在這裏實現,只能是因爲強烈的x86內存模型。如果您將這些代碼移到硬內存模型的硬件上,它將不會像實現一樣工作。你需要實施障礙。可能做,但不是完全微不足道。

雙重檢查鎖定雖然沒有意義。刪除它並用一個關鍵部分保護所有內容。你正在旋轉一個線程,一個非常昂貴的任務。關鍵部分的潛在爭用可以忽略不計。

+0

「使用關鍵部分」但我確實使用了關鍵部分。 – zig

+0

噢,別介意,按照你的方式,完成所有不必要的複雜性 –

+1

因此,當我調用DLLProcess(甚至是來自主UI)時,你建議阻止每個調用,每次都有關鍵部分?如果是的話,這是我如何開始,但我認爲這將是一個非常糟糕的主意。 – zig