我有一个Delphi DLL,需要从我的主UI应用程序或工作线程调用.
每次调用DLL时我都不想调用LoadLibrary/FreeLibrary.但是,我也不想在我的应用程序初始化部分加载它.因为在应用程序的生命周期中我可能根本不使用DLL.
所以我需要的是初始化和加载DLL的第一个调用者(线程或主UI).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.
mghie.. 5
您需要修改代码,使得InitDLLByFirstCall
只有在完成所有初始化之后才设置在开始时检查的条件变量.因此DLL句柄是一个糟糕的选择.
其次,您需要使用相同的条件变量外部和关键部分的内-如果你使用DLLInitialized
了这一点,那么确实没有为任何一个使用DLLInitialized_OK
也不DLLModule
.
为了让事情更容易推理,你应该尽量避免使用最少数量的变量.像下面这样的东西应该工作:
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
变量使用整数或枚举,具有未初始化,失败和成功的不同值.
您需要修改代码,使得InitDLLByFirstCall
只有在完成所有初始化之后才设置在开始时检查的条件变量.因此DLL句柄是一个糟糕的选择.
其次,您需要使用相同的条件变量外部和关键部分的内-如果你使用DLLInitialized
了这一点,那么确实没有为任何一个使用DLLInitialized_OK
也不DLLModule
.
为了让事情更容易推理,你应该尽量避免使用最少数量的变量.像下面这样的东西应该工作:
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
变量使用整数或枚举,具有未初始化,失败和成功的不同值.