【问题标题】:Delphi link to windows dll statically or dynamicallyDelphi静态或动态链接到windows dll
【发布时间】:2013-07-18 14:35:56
【问题描述】:

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

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

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

【问题讨论】:

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


    【解决方案1】:

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

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

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

    我的建议如下:

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

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

    【讨论】:

    • AFAIR 我的调试会话这些存根仅由 JMP FAR 指令组成。所以它是单个额外字节(JMP FAR DIRECT - $E9 操作码)或 5 个字节(JMP FAR indIRECT + 指针变量的地址)。但 AFAIR 那是前一种选择。所以......好吧......它们是存根,但那些存根只需要一个额外的字节。这比调用GetProcAddress 并检查错误的序幕要少得多。然后在那里添加查找并选择由名称给出的特定版本的 DLL,运行时绑定在代码中变得更大。
    • @Arioch 还有导入表,但大小含义类似,就像我说的那样。
    • 我认为它们并不相似。将 ID 存储为 utf-16 常量而不是单独的 8 位 ANSI 导入表会超重那些 4 字节指针。然后通常会为每个单独的过程提供自定义代码,这些代码比这更重要。我不相信显式链接会导致比操作系统提供的二进制文件更小的实际应用程序。
    • @Arioch 定义类似。很难为这样一个不精确的术语争论。我的显式链接是使用实用程序类基于表的。将名称存储为 AnsiString 并且它很接近。在程序中所有其余代码的上下文中,任何一种方式的影响都可以忽略不计。更重要的是,导入表可以在使用后分页。显式链接不能。各种各样的差异,没有一个加起来太多。
    • 谢谢,关于图书馆管理,我在基于 Unix 的环境中更加自如,即便如此,我的图书馆知识也相当有限。您的回答澄清了我不确定的各种微妙之处。非常感谢并感谢您解码我对静态/动态的不当使用,我现在需要阅读更多内容。
    【解决方案2】:

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

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

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

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

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

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

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

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

    【讨论】:

    • 这对我来说似乎有点迂腐。很明显,提问者说的是静态的,意思是隐式的,说的是运行时的,意思是显式的。
    • 我不太确定,@David。 Tjenks 谈到了增加的 EXE 大小,并且静态链接是唯一在这方面有任何显着影响的类型。
    • 人们通常使用术语静态来代替 dll 链接的隐式或加载时间。因此混乱。 MS无法对术语下定决心也无济于事。隐式/显式或加载时间/运行时间。
    • 我的意思是,如果这些东西都有一个名字,对每个人来说都会更容易!令人沮丧的是,Windows 团队使用一个名称而 VS 团队使用另一个名称。
    • 我最后将其称为“早期绑定”和“后期绑定”,因为 1)这不是绑定吗? 2)在我看来,“早”和“晚”巧妙地传达了这种相对性,即总有一些事情甚至更早或更晚;-)
    【解决方案3】:

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

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

    • DLL1 名称:
        • 将“aaaaa”写入变量 $1234
        • 在变量 $5678 中处理“bbbbb”

    .

    • DLL2 名称:
        • 在变量 $4567 中处理“ccccc”

    ...等等。


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

       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 无关。

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

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


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

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

    HTH。

    【讨论】:

    • 没有针对动态库的静态链接。动态库是动态的,而不是静态的。动态链接使用动态库,静态链接使用静态库。您将加载时动态链接与运行时动态链接混淆了。它们都是动态的。
    • @RobKennedy 从技术上讲你是对的。但是这个词在早期就被使用了,尽管开始不正确和模棱两可。这是为 TS 提供“当链接到位于 System32 中的 Microsoft Windows dll 文件时……遵循静态链接方法”的问题。即使理论上不正确,我也会尽量冗长,并说明混淆是如何产生的。
    猜你喜欢
    • 2010-12-02
    • 1970-01-01
    • 1970-01-01
    • 2017-06-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多