Delphi应用程序是否有可能优雅地处理缺失的DLL文件?

7

请问是否有可能优雅地测试和处理 Delphi 应用程序中缺少的 .dll 文件? 例如,我的代码有以下函数声明:

function KFUNC(Arg1, Arg2, Arg3, Arg4: DWord): longint stdcall; external 'KL2DLL32.DLL' name '_KFUNC@16';

当然,这需要在系统上找到KL2DLL32.DLL动态链接库文件,否则我的应用程序就不能启动。我想知道是否有一种不同的编码方式,让我的应用程序可以测试dll文件是否存在,然后相应地处理。显然,目标是使我的应用程序在没有dll文件的情况下仍然能够正常启动。谢谢。


1
使用LoadLibrary并获取过程地址。 - Jerry Dodge
2个回答

12

你的导入结果会导致函数使用所谓的 load-time(或者叫隐式)链接。这意味着可执行文件包含元数据,告诉操作系统加载 DLL 并绑定你命名的函数。如果这个 load-time 链接过程失败了,那么可执行文件将无法被加载。

你有几个选项可以避免 load-time 链接,从而使你的程序对链接故障具有弹性。

延迟加载 DLL

在你的函数导入中添加 delayed 指令。文档中写道:

为了将包含该函数的库的加载推迟到实际需要该函数的时候,向导入函数添加 delayed 指令:

function ExternalMethod(const SomeString: PChar): Integer; stdcall; 
  external 'cstyle.dll' delayed;

使用delayed可以确保包含导入函数的库在应用程序启动时不会被加载,而是在第一次调用该函数时才加载。

文档包含其他有用的主题,详细介绍了如何处理错误:

显式加载并绑定到DLL

delayed指令只是一种使编译器安排显示加载您的DLL的简洁方式。您可以自己手动使用LoadLibraryGetProcAddress来执行相同的操作。

  1. 调用LoadLibrary以加载DLL。提供一个DLL的完整路径,或者只提供其名称。在后一种情况下,您依靠DLL搜索顺序来定位DLL。调用LoadLibrary将产生一个模块句柄。
  2. 调用GetProcAddress以获取命名函数指针的地址。您必须提供第1步中的模块句柄。
  3. 调用从步骤2返回的函数指针。
  4. 当您不再需要调用该函数时,请使用FreeLibrary卸载DLL。

在每个步骤中,您必须检查返回值以防错误。如何处理错误在MSDN文档中为每个Win32 API函数记录(如上所述)。例如,如果找不到DLL,则LoadLibrary返回0。您必须检测并相应地处理后果。

讨论

尽管delayed指令非常方便,但我个人从未使用过它。根据我的经验,每当我需要显式链接时,我总是发现需要一些额外的灵活性,而这种灵活性不被delayed所提供。也许我的需求很特殊,但如果您发现自己倾向于显式调用LoadLibraryGetProcAddress,那也不要感到惊讶。

例如,就在今天,我发现自己正在使用LoadLibraryEx,因为我想传递LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR标志。当您使用delayed时,这种细粒度的控制是不可用的。


还应该提到,“delayed”指令是在“后期”的Delphi版本中引入的。它在Delphi 2007之前不可用(我不知道确切的引入时间)。 - dummzeuch
根据Rob的说法,现在是2010年。我个人认为2010年并不算是“较晚的Delphi版本”! - David Heffernan
这就是为什么我在“later”中加了引号的原因。 - dummzeuch

5
当函数声明如此时,你的程序对于缺失的DLL无能为力。操作系统在程序代码开始执行之前尝试解析导入的DLL函数,因此你无法编写任何代码来处理它。
但是从Delphi 2010开始,你可以修改函数的声明以使用新的“延迟加载”特性。将“delayed”指令添加到声明的末尾:
function KFUNC(Arg1, Arg2, Arg3, Arg4: DWord): longint stdcall;
  external 'KL2DLL32.DLL' name '_KFUNC@16' delayed;

如果你使用的是较旧版本的Delphi,那么你唯一的选择就是在运行时加载DLL和函数,然后处理错误。使用延迟加载的另一个好处是有SetDliNotifyHook2SetDliFailureHook2函数,它们允许你分配钩子,以便你可以处理运行时加载通知和失败。所以,如果某个给定的DLL,甚至是一个给定的函数,在运行时找不到,你可以记录一个错误,甚至替换它为另一个DLL句柄或函数指针来满足加载需求。这两种选项在另一个关于仅在需要时使用DLL的问题中有更详细的讨论。

6
delayed 特性只是一个编译器/RTL包装器,用于 LoadLibrary()GetProcAddress() 函数。使用 delayed 的另一个好处是有 SetDliNotifyHook2()SetDliFailureHook2() 函数,可以分别分配钩子以处理运行时加载通知和失败。因此,如果在运行时找不到给定的 DLL,甚至是给定的函数,您可以记录错误,甚至替换它们以满足加载。 - Remy Lebeau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接