【问题标题】:How does the Import Library work? Details?导入库如何工作?细节?
【发布时间】:2011-04-04 03:52:10
【问题描述】:

我知道这对极客来说似乎很基础。但我想把它说得一清二楚。

当我想使用 Win32 DLL 时,我通常只调用 LoadLibrary() 和 GetProcAdderss() 等 API。但是最近在用DirectX9开发,需要添加d3d9.libd3dx9.lib等文件。

我听说 LIB 用于静态链接而 DLL 用于动态链接。

所以我目前的理解是 LIB 包含方法的实现,并在链接时作为最终 EXE 文件的一部分静态链接。虽然 DLL 是在运行时动态加载的,并且不是最终 EXE 文件的一部分。

但有时,有一些 LIB 文件伴随 DLL 文件,所以:

  • 这些 LIB 文件有什么用途?
  • 他们如何实现他们的目标?
  • 是否有任何工具可以让我检查这些 LIB 文件的内部?

更新 1

查了wikipedia,我记得这些LIB文件叫import library。 但我想知道它如何与我的主应用程序和动态加载的 DLL 一起工作。

更新 2

正如RBerteig 所说,与DLL 一起生成的LIB 文件中有一些存根代码。所以调用顺序应该是这样的:

我的主应用程序 --> LIB 中的存根 --> 真正的目标 DLL

那么这些 LIB 中应该包含哪些信息?我可以想到以下几点:

  • LIB 文件应包含相应 DLL 的完整路径;因此,DLL 可以由运行时加载。
  • 每个 DLL 导出方法的入口点的相对地址(或文件偏移量?)应在存根中进行编码;因此可以进行正确的跳转/方法调用。

我说得对吗?还有什么吗?

顺便说一句:有没有可以检查导入库的工具?如果我能看到它,就不会再有任何疑问了。

【问题讨论】:

  • 我看到没有人解决您问题的最后一部分,即可以检查导入库的工具。使用 Visual C++,至少有两种方法可以做到这一点:lib /list xxx.liblink /dump /linkermember xxx.lib。见this Stack Overflow question
  • 另外,与liblink 实用程序相比,dumpbin -headers xxx.lib 提供了一些更详细的信息。

标签: c++ c windows visual-c++


【解决方案1】:

链接到 DLL 文件可以在 compile 链接时隐式发生,或者在运行时显式发生。无论哪种方式,DLL 最终都会加载到进程内存空间中,并且其所有导出的入口点都可供应用程序使用。

如果在运行时显式使用,您可以使用LoadLibrary()GetProcAddress() 手动加载 DLL 并获取指向您需要调用的函数的指针。

如果在构建程序时隐式链接,则程序使用的每个 DLL 导出的存根会从导入库链接到程序,并且这些存根会随着 EXE 和 DLL 在进程启动时加载而更新. (是的,我在这里简化了很多......)

这些存根需要来自某个地方,并且在 Microsoft 工具链中,它们来自称为 导入库 的特殊形式的 .LIB 文件。所需的 .LIB 通常与 DLL 同时构建,并且包含从 DLL 导出的每个函数的存根。

令人困惑的是,同一库的静态版本也将作为 .LIB 文件提供。除了作为 DLL 的导入库的 LIB 通常比匹配的静态 LIB 更小(通常小得多)之外,没有简单的方法可以区分它们。

如果您使用 GCC 工具链,顺便说一下,您实际上并不需要导入库来匹配您的 DLL。移植到 Windows 的 Gnu 链接器版本可以直接理解 DLL,并且可以动态合成大多数所需的存根。

更新

如果您无法抗拒了解所有具体细节和实际情况,MSDN 总能提供帮助。 Matt Pietrek 的文章An In-Depth Look into the Win32 Portable Executable File Format 非常完整地概述了 EXE 文件的格式以及它是如何加载和运行的。自从它最初出现在 MSDN Magazine ca. 以来,它甚至被更新为涵盖 .NET 和更多内容。 2002.

此外,了解如何准确了解程序使用的 DLL 也会很有帮助。用于此的工具是 Dependency Walker,又名depends.exe。它的一个版本包含在 Visual Studio 中,但最新版本可从其作者http://www.dependencywalker.com/ 处获得。它可以识别在链接时指定的所有 DLL(早期加载和延迟加载),它还可以运行程序并监视它在运行时加载的任何其他 DLL。

更新 2

我已经改写了一些较早的文本以在重新阅读时对其进行澄清,并使用艺术术语 implicitexplicit linking 与 MSDN 保持一致。

因此,我们可以通过三种方式使库函数可供程序使用。显而易见的后续问题是:“我如何选择哪种方式?”

静态链接是大部分程序本身的链接方式。列出所有目标文件,并通过链接器将它们一起收集到 EXE 文件中。在此过程中,链接器会处理一些琐事,例如修复对全局符号的引用,以便您的模块可以调用彼此的函数。库也可以静态链接。组成库的目标文件由图书馆员收集在一个 .LIB 文件中,链接器在该文件中搜索包含所需符号的模块。静态链接的一个效果是只有程序使用的库中的那些模块才链接到它。其他模块被忽略。例如,传统的 C 数学库包含许多三角函数。但是如果你链接它并使用cos(),你不会得到sin()tan() 的代码副本,除非你也调用了这些函数。对于具有丰富特性的大型库,选择性地包含模块很重要。在许多平台(例如嵌入式系统)上,库中可用的代码总大小可能比设备中存储可执行文件的可用空间大。如果没有选择性地包含,就很难管理为这些平台构建程序的细节。

但是,在每个运行的程序中都有一个 same 库的副本会给通常运行大量进程的系统带来负担。使用正确的虚拟内存系统,具有相同内容的内存页只需要在系统中存在一次,但可以被许多进程使用。这为增加包含代码的页面可能与尽可能多的其他正在运行的进程中的某个页面相同的机会创造了一个好处。但是,如果程序静态链接到运行时库,那么每个程序都有不同的函数组合,每个函数都布置在不同位置的进程内存映射中,并且没有多少可共享的代码页,除非它是一个完全独立的程序运行超过进程。因此,DLL 的想法获得了另一个主要优势。

库的 DLL 包含其所有功能,可供任何客户端程序使用。如果许多程序加载该 DLL,它们都可以共享其代码页。每个人都赢了。 (嗯,直到你用新版本更新一个 DLL,但这不是这个故事的一部分。谷歌 DLL Hell 故事的那一面。)

因此,在规划新项目时要做出的第一个重大选择是动态和静态链接之间的选择。使用静态链接,您需要安装的文件更少,并且您不会受到第三方更新您使用的 DLL 的影响。但是,您的程序更大,它不是 Windows 生态系统的好公民。使用动态链接,您需要安装更多文件,您可能会遇到第三方更新您使用的 DLL 的问题,但您通常对系统上的其他进程更友好。

DLL 的一大优点是无需重新编译甚至重新链接主程序即可加载和使用它。这可以允许第三方库提供商(例如 Microsoft 和 C 运行时)修复其库中的错误并分发它。一旦最终用户安装了更新后的 DLL,他们会立即在使用该 DLL 的所有程序中受益于该错误修复。 (除非它破坏了东西。参见 DLL Hell。)

另一个优点来自隐式加载和显式加载之间的区别。如果您进行显式加载的额外工作,那么在编写和发布程序时,DLL 甚至可能不存在。例如,这允许可以发现和加载插件的扩展机制。

【讨论】:

  • 删除我的帖子并对此表示赞同,因为您解释的方式比我做得更好;)很好的答案。
  • @RBerteig:感谢您的出色回答。稍微更正一下,根据这里(msdn.microsoft.com/en-us/library/9yd93633.aspx),有两种类型的动态链接到 DLL,加载时隐式链接运行时显式链接。没有编译时链接。现在我想知道传统的 Static Linking(链接到包含完整实现的 *.lib 文件)和 Load-Time dynamic linking 到 DLL(通过导入库)?
  • 继续:静态链接加载时动态链接的优缺点是什么?似乎这两种方法都在进程开始时将所有必要的文件加载到地址空间中。为什么我们需要其中 2 个?谢谢。
  • 您也许可以使用“objdump”之类的工具来查看 .lib 文件并确定它是导入库还是真正的静态库。在 Linux 上交叉编译到 Windows 目标时,可以在 .a 文件(.lib 文件的 mingw 版本)上运行“ar”或“nm”,并注意导入库具有通用 .o 文件名且没有代码(只是一个'jmp'指令),而静态库里面有很多函数和代码。
  • 小修正:您也可以在运行时隐式链接。 Linker Support for Delay-Loaded DLLs 详细解释了这一点。如果您想动态更改 DLL 搜索路径,或优雅地处理导入解析失败(例如,以支持新的操作系统功能,但仍可在旧版本上运行),这将很有帮助。
【解决方案2】:

这些 .LIB 导入库文件用于以下项目属性 Linker->Input->Additional Dependencies,在构建一堆 dll 时,这些 dll 在链接时需要由导入库 .LIB 文件提供的附加信息。在下面的示例中,为了不出现链接器错误,我需要通过它们的 lib 文件引用 dll 的 A、B、C 和 D。 (请注意,要让链接器找到这些文件,您可能需要在 Linker->General->Additional Library Directories 中包含它们的部署路径,否则您将收到关于无法找到任何提供的 lib 文件的构建错误。)

如果您的解决方案正在构建所有动态库,您可能已经能够通过依赖Common Properties->Framework and References 对话框下公开的引用标志来避免这种显式依赖规范。这些标志似乎使用 *.lib 文件代表您自动执行链接。

然而,正如它所说的 Common 属性,它不是配置或平台特定的。如果您需要像在我们的应用程序中那样支持混合构建场景,我们有一个构建配置来呈现静态构建和一个特殊配置,该配置构建了作为动态库部署的程序集子集的受限构建。我曾在各种情况下使用Use Library Dependency InputsLink Library Dependencies 标志设置为true 来构建东西,后来意识到要简化事情,但是在将我的代码引入静态构建时,我引入了大量链接器警告,构建令人难以置信静态构建速度慢。我最终引入了一堆这样的警告......

warning LNK4006: "bool __cdecl XXX::YYY() already defined in CoreLibrary.lib(JSource.obj); second definition ignored  D.lib(JSource.obj)

我最终使用Additional Dependencies 的手动规范来满足动态构建的链接器,同时通过不使用减慢它们速度的公共属性来保持静态构建器的快乐。当我部署动态子集构建时,我只部署 dll 文件,因为这些 lib 文件仅在链接时使用,而不是在运行时使用。

【讨论】:

    【解决方案3】:
    【解决方案4】:

    库分为三种:静态库、共享库和动态加载库。

    静态库在链接阶段与代码链接,因此它们实际上位于可执行文件中,与共享库不同,共享库文件中只有存根(符号)可以在运行时加载的共享库文件中查找主函数被调用之前的时间。

    动态加载的库与共享库非常相似,只是它们会在您编写的代码需要时加载。

    【讨论】:

    • @谢谢扎切克。但我不确定你关于共享库的说法。
    • @smwikipedia:Linux 有它们,我使用它们,所以它们绝对存在。另请阅读:en.wikipedia.org/wiki/Library_(computing)
    • 这是一个微妙的区别。共享库和动态库都是 DLL 文件。区别在于它们何时加载。共享库由操作系统与 EXE 一起加载。动态库由调用LoadLibrary()的代码和相关API加载。
    • 我从 [1] 中了解到 DLL 是 Microsoft 对共享库概念的实现。 [1]:en.wikipedia.org/wiki/Dynamic-link_library#Import_libraries
    • 我不同意这是一个细微的区别,从编程的角度来看,共享库是否被动态加载会有很大的不同(如果它是动态加载的,那么你必须添加样板代码才能访问函数)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多