当我的程序打开时,在我的任何代码实际运行之前,它会自动尝试加载它导入的各种 DLL。它在应用程序所在的文件夹中查找,然后在一些特定的位置,如 \Windows 和 \Windows\System32。
如果我想使用一些自定义 DLL,但我不想将应用程序的文件夹与它们混淆,有没有办法将它们安装到子文件夹中,然后将一些东西放入 EXE 中,告诉它在哪里查找?
当我的程序打开时,在我的任何代码实际运行之前,它会自动尝试加载它导入的各种 DLL。它在应用程序所在的文件夹中查找,然后在一些特定的位置,如 \Windows 和 \Windows\System32。
如果我想使用一些自定义 DLL,但我不想将应用程序的文件夹与它们混淆,有没有办法将它们安装到子文件夹中,然后将一些东西放入 EXE 中,告诉它在哪里查找?
您必须更改PATH
环境变量。尝试使用SetDllDirectory()
功能。否则,您将不得不动态加载您的 DLL。
另请参阅此问题以避免更多可能的问题。
这是您需要了解的有关 DLL 搜索顺序的所有信息。请注意不要引入安全问题。如果您搜索不安全的位置,攻击者可以将恶意 DLL 放在那里并让您的程序加载并执行它。
我实际上喜欢动态加载的 DLLS,并且只有一个包装器单元,所以我可以调用 DLL 函数,就好像它们是直接在其中编译的一样。
通过让每个包装器函数调用我的本地 LoadDLLLibrary,我避免了加载/卸载的性能损失,像这样:
function LoadDLLLibrary: Boolean;
begin
if MyDLLLib = 0 then
MyDLLLib := LoadLibrary('path to my dll'); // Only load it once.
Result := MyDLLLib <> 0;
end;
每个包装器都调用 LoadDLLLibrary(实际上只执行一次),然后调用 GetProcAddress。就像这样:
procedure DoSomeDLLStuff;
var
DLLProc: TExecuteDoSomeDLLStuffProc;
begin
LoadDLLLibrary;
try
if MyDLLLib <> 0 then
begin
DLLProc := GetProcAddress(MyDLLLib , PROC_SomeDLLSTuff);
DLLProc; // run it
end;
finally
// No need to unload, it'll get unloaded in finalization.
end;
end;
并且一直在底部......
initialization
MyDLLLib := 0;
finalization
UnLoadDLLLibrary; // Final unload, as we let it stick around instead of freeing it all the time.
end.
所以最终的结果是我只加载了一次 DLL,然后卸载了一次。对于动态加载但执行很多的 DLL 非常方便。
As I said in my comments to the question, if you are relying on static dependencies and thus loading by Windows, then you are stuck with using the standard ways in which Windows searches for dlls. And if you do not want to change the windows' path permanently, you could try running your app from a bat/cmd file and change the path just before starting your app. AFAIK that should limit the change of the path to the (duration of the) cmd instance started to execute the bat/cmd file.
More flexibility can be obtained though if you are able to change to using dynamic dependencies (remove your bpls from the required list?). As with LoadLibrary, bpls compiled to use runtime packages can be loaded dynamically as well. It is what most delphi bpl based plugin systems rely on.
(Un)Loading bpls dynamically is done using (Un)LoadPackage. LoadPackage loads the package specified by the Name parameter (using SafeLoadLibrary), checks for duplicate units, and calls the initialization blocks of all units contained in the package.
To make sure all Register procedures in a dynamically loaded bpl are called, you need to enumerate the units using a GetPackageInfo call providing a call back function.
BTW: Code samples are excerpts from a plugin system developed during a dynamic applications workshop by Mark Miller (CodeRush's developer/architect) during a 2001 conference. The code used to be online, but I can no longer find it there...
var
localModuleHandle: HModule;
begin
try
localModuleHandle := LoadPackage(packageName);
//GetPackageInfo accesses the given package's info table and enumerates
// all the contained units and required packages
Flags := ufAllUnits;
GetPackageInfo(localModuleHandle, Pointer(localModuleHandle), Flags, PackageIsLoadingProc);
except
on e: Exception do
Application.MessageBox(PChar(e.Message), PChar(sError), MB_OK + MB_ICONWARNING);
end;
end;
procedure PackageIsLoadingProc(const Name: string; NameType: TNameType;
Flags: Byte; Param: Pointer);
type
TRegisterProc = procedure;
var
RegisterProc: TRegisterProc;
localName: String;
begin
// Flags:
// ufMainUnit = $01;
// ufPackageUnit = $02;
// ufWeakUnit = $04;
// ufOrgWeakUnit = $08;
// ufImplicitUnit = $10;
// ufWeakPackageUnit = ufPackageUnit or ufWeakUnit;
if NameType = ntContainsUnit then
begin
localName := LowerCase(Name);
if Length(localName) > 0 then
localName[1] := UpCase(localName[1]);
@RegisterProc := GetProcAddress(HModule(Param),
PChar('@' + localName + '@Register$qqrv'));
if @RegisterProc <> nil then
RegisterProc;
end;
end;