1

我知道在加载时隐式链接到库会导致性能提高,因此我想知道在编译时以这种方式链接是否是一种好习惯,从而与显式链接相比增加可执行文件大小(诚然这只是微不足道的)在运行时。我的问题是,当链接位于 System32 中的 Microsoft Windows dll 文件时,在加载时链接是否“更好”,因为您可以确定这些库将存在或遵循显式方法?

使用的语言是 Delphi (pascal),有问题的库是 WTsAPI32.dll - 终端服务。

编辑:正如所指出的 - 我选择的语言不正确并且已被修改。另外,由于在 Unix 中只有每个广泛链接到库,我对可执行文件大小的评论可以省略,我相信当时我实际上是指将库代码捆绑到可执行文件中的静态链接,我现在意识到这一点使用 dll 文件时是不可能的(DUH!)。谢谢大家。

4

3 回答 3

5

将两种形式的 DLL 链接命名为隐式和显式可能更好。隐式链接就是您所说的静态链接。显式链接就是您所说的运行时链接

对于隐式链接,链接器将条目写入可执行文件的导入表。此导入表是加载程序在模块加载时用于解析 DLL 导入的元数据。每个只有几个字节大小的隐式导入都包含一个存根函数。隐式链接对可执行文件大小的影响可以忽略不计。

通过显式链接,导入函数的地址通过调用 GetProcAddress 来解析。当程序员选择时进行此调用。如果 DLL 或函数无法解析,程序员可以编写回退行为代码。我估计显式链接类似于隐式链接有大小影响。如果函数地址被评估一次并在调用之间记住,那么性能特征类似于隐式链接。

我的建议如下:

  1. 更喜欢隐式链接。编码更方便。
  2. 如果 DLL 可能不存在,请使用显式链接。
  3. 如果必须使用完整路径加载 DLL,请使用显式链接。
  4. 如果要在程序执行期间卸载 DLL,请使用显式链接。

您特别提到了 Windows DLL。您可以放心地假设他们会在场。如果 user32.dll 丢失,请不要尝试编写代码以允许您的程序运行。某些功能可能不存在于旧版本的 Windows 中。如果您支持那些旧版本,则需要使用显式链接并提供回退。确定您支持的版本并使用 MSDN 以确保某个功能在您的最低支持平台上可用。

于 2013-07-18T18:16:31.870 回答
3

如果您仅有的两个选项是静态链接运行时动态链接,那么后者是与 Windows DLL 链接的最佳选择,因为它是您唯一的选择。您不能静态链接到 DLL,因为 DLL 专门用于动态链接;这就是D的含义。Microsoft 不为 OS 模块提供静态库,因此您无法静态链接到它们。

但这些通常不是您唯一的两个选择。还有第三种,即加载时动态链接

在 Delphi 中,您可以通过标记函数声明external并指定函数所在的 DLL 的名称来使用加载时动态链接。如果您使用该函数,则在模块的导入表中创建一个条目,当操作系统加载您的模块时,它会读取表,加载引用的 DLL,查找函数的地址,并将地址存储在程序的内存映像,以便您的程序可以直接调用它。

您可以通过声明函数指针来使用运行时动态链接,然后在调用函数之前使用LoadLibraryGetProcAddress查找函数的地址。在较新的 Delphi 版本中,您还可以以加载时动态链接使用的相同样式声明函数,但随后将其标记为delay. 在这种情况下,Delphi 运行时库将在您第一次调用该函数时代表您调用LoadLibraryGetProcAddress

大小差异可以忽略不计。运行时动态链接要求您的程序包含加载和链接到库的代码,但加载时动态链接将更多函数引用存储在导入表中。

面对不确定的 DLL 可用性,运行时动态链接提供了更大的灵活性。使用加载时动态链接,如果缺少 DLL,或者如果它没有导入表中提到的所有功能,那么操作系统将无法加载您的程序 - 您的任何代码都不会运行。但是,使用运行时动态链接,您就有机会从问题中恢复过来。您可以禁用缺少的 DLL 所依赖的程序的某些部分,或者您可以在非标准位置搜索 DLL,或者您可以提供缺少函数的替代实现。

如果您调用的函数是程序运行能力不可或缺的一部分,并且有充分的理由期望这些函数出现在您的程序安装的任何位置,那么您应该选择在加载时链接。它允许您编写更简单的代码。如果您在安装程序中检查的特定版本的 Windows 上提供了所需的功能,或者如果它们是由您随程序分发的 DLL 提供的,则您可以确信您将拥有所需的功能。

另一方面,如果您调用的函数是可选的,那么您应该更喜欢在运行时进行链接。使用它来加载插件,或在保持向后兼容性的同时利用高级操作系统功能。(例如,您可能希望在存在 Windows Vista 主题支持时利用它,但仍允许您的程序在 Windows XP 上运行。)

于 2013-07-18T20:26:17.413 回答
0

为什么您认为编译时链接到动态库会增加 EXE 大小?我相信你被一些糟糕的术语选择误导了,这些术语从很久以前就用于 Windows 编程。让我们更好地使用相对术语“早期绑定”和“后期绑定”来代替应该搜索过程名称、编译器/加载器或程序员的自定义代码的选择。

使用早期绑定(又名静态链接到动态)您的 EXE 包含值(在特殊表中):

  • DLL1 名称:
      • 程序“aaaaa”到变量 $1234
      • 将“bbbbb”程序放入变量 $5678

.

  • DLL2 名称:
      • 程序“ccccc”到变量 $4567

...等等。


现在,当您将其转换为运行时加载(针对动态的动态链接)时,它看起来像

   VarH1 := SafeLoatLibrary(DLL1 Name);
   if Error-Loading-DLL then do-error-handling;

   Var1234 := GetProcAfdress(VarH1, "aaaaa");
   if Error-Searching-For-Function then do-error-handling;

   Var5678 := GetProcAfdress(VarH1, "bbbbb");
   if Error-Searching-For-Function then do-error-handling;

等等。

显然,在后一种情况下,您的 EXE 包含所有这些值,就像在第一种情况下一样,但更重要的是 - 它包含大量处理这些值的代码,而这在以前是不存在的。

因此,虽然 EXE 的大小差异对于今天的内存大小来说并不是很大,但它仍然有利于早期绑定(针对动态库的静态编译)。

那么后期绑定有什么好处呢?例如,您可以从不同的路径加载不同的 DLL,在运行时由配置确定 - DLL Hell 的灵活性和避免(有趣的是,避免 DLL Hell 的概念与体积节省的概念相反)。如果 DLL 加载失败而静态绑定的 EXE 将无法加载,您可以使您的应用程序使用有限的功能工作 - 优雅降级概念。至少你可以为用户提供比 Windows 更好的、充满语义的、错误消息。


最后一句话,你从哪里得到 EXE 大小的概念。我相信你从谈论中误会了——注意!-针对静态的静态链接。也就是说,OBJ/LIB/DCU 文件不是分发的一部分,而只是临时代码容器,最终会在单一的 EXE 中占据一席之地。那么是的 - 那么您的 EXE 本身就拥有所有这些库,因此会变得更大。然而,这种情况与动态库无关 - DLL。

以前选择的措辞在两个密切相关的主题中过度使用了静态/动态术语:如何加载库(编译时与运行时)以及库内的函数如何定位(或绑定)。由开发人员的自定义编码 ro 由某些操作系统提供或在源代码的第一行开始执行之前编译器提供的工具集方式)。

由于这种模糊性,那些接近但不同的概念开始重叠,有时这会导致完全混乱。


现在,在现代 Windows 版本中,更多的静态链接可能会给您带来什么。那是WinSxS 文件夹Novadays Windows 倾向于保留每个系统 DLL 的多个版本,您的程序可能会要求它的特定版本(而在System32文件夹中会有您的程序可能不习惯的最新版本。然后你可以制作一个特殊的 MANIFEST 资源并将其编译为 EXE,要求 Windows 加载不是名称的 DLL,而是按名称+版本。您也可以通过动态加载复制该功能,但使用 Windows 提供的工具集要容易得多。

现在,您可以决定哪些选项对您的特定情况重要或不重要,并做出更明智的选择。

HTH。

于 2013-07-18T14:40:41.273 回答