Delphi以静态或动态方式链接到windows dll

时间:2013-07-18 14:35:56

标签: windows delphi dll static-linking dynamic-linking

我知道在加载时隐式链接到库会导致性能提升,因此我想知道在编译时以这种方式链接是否是一种好的做法,从而增加了可执行文件的大小(不可否认,这只是边际)在运行时显式链接。我的问题是当链接到位于System32中的Microsoft Windows DLL文件时,在加载时链接是否“更好”,因为您可以确定库存在或遵循显式方法?

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

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

3 个答案:

答案 0 :(得分:5)

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

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

通过显式链接,通过调用GetProcAddress解决导入函数的地址。这个调用是在程序员选择的时候进行的。如果无法解析DLL或函数,程序员可以编写回退行为。显式链接存在大小影响,我估计它与隐式链接类似。如果函数地址被评估一次并在调用之间被记住,则性能特征类似于隐式链接。

我的建议如下:

  1. 首选隐式链接。代码更方便。
  2. 如果DLL可能不存在,请使用显式链接。
  3. 如果必须使用完整路径加载DLL,请使用显式链接。
  4. 如果要在程序执行期间卸载DLL,请使用显式链接。
  5. 您特别提到了Windows DLL。你可以放心地假设他们会在场。不要尝试编写代码以允许您的程序运行,以防user32.dll丢失。旧版Windows中可能不存在某些功能。如果您支持这些旧版本,则需要使用显式链接并提供后备。确定您支持的版本并使用MSDN确保您的最低支持平台上有一个功能。

答案 1 :(得分:3)

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

但那些典型的不是你唯一的两个选择。还有第三种,即加载时动态链接

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

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

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

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

如果您正在呼叫的功能积分到您的程序的操作能力,并且有足够的理由期望在您的程序安装的任何地方出现这些功能,那么您应该选择链接到加载时间。它允许您编写更简单的代码。您可以确信,如果您在安装程序中检查的特定版本的Windows上可用,或者如果它们是由您在程序中分发的DLL提供的,那么您将拥有所需的功能。

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

答案 2 :(得分: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在源的第一行开始执行之前,OS提供的或编译器提供的工具集方式。

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


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

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

HTH。