2015-12-21 51 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