我知道这对极客来说似乎很基本。但我想让它一清二楚。
当我想使用 Win32 DLL 时,通常我只调用 LoadLibrary() 和 GetProcAdderss() 之类的 API。但最近,我正在使用 DirectX9 进行开发,我需要添加 d3d9.lib 、 d3dx9.lib 等文件。
我听说 LIB 用于静态链接而 DLL 用于动态链接。
所以我目前的理解是 LIB 包含方法的实现,并在链接时作为最终 EXE 文件的一部分静态链接。虽然 DLL 是在运行时动态加载的,并且不是最终 EXE 文件的一部分。
但有时,DLL 文件 中会附带 一些 LIB 文件,因此:
- 这些 LIB 文件是做什么用的?
- 他们如何实现他们的目的?
- 有什么工具可以让我检查这些 LIB 文件的内部结构吗?
更新 1
查了维基百科后,我记得这些 LIB 文件被称为 import library 。但我想知道它如何与我的主应用程序和动态加载的 DLL 一起工作。
更新 2
正如RBerteig 所说,与DLL 一起生成的LIB 文件中有一些存根代码。所以调用顺序应该是这样的:
我的主应用程序 –> LIB 中的存根 –> 真正的目标 DLL
那么这些 LIB 中应该包含哪些信息呢?我可以想到以下几点:
- LIB 文件应包含相应 DLL 的完整路径;所以DLL可以由运行时加载。
- 每个 DLL 导出方法的入口点的相对地址(或文件偏移量?)应该在存根中编码;因此可以进行正确的跳转/方法调用。
我说得对吗?还有什么吗?
顺便说一句:有没有可以检查导入库的工具?如果我能看到它,就不会再有任何疑问了。
原文由 smwikipedia 发布,翻译遵循 CC BY-SA 4.0 许可协议
链接到 DLL 文件可以在编译链接时 隐式 发生,也可以在运行时 显式 发生。无论哪种方式,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
我已经改写了一些较早的文本以在重新阅读时对其进行澄清,并使用艺术 隐式 和 显式链接 的术语与 MSDN 保持一致。
因此,我们可以通过三种方式使库函数可供程序使用。显而易见的后续问题是:“我如何选择哪种方式?”
静态链接是大部分程序本身的链接方式。列出所有目标文件,并通过链接器将它们一起收集到 EXE 文件中。在此过程中,链接器会处理一些琐事,例如修复对全局符号的引用,以便您的模块可以调用彼此的函数。库也可以静态链接。组成库的目标文件由图书馆员收集在一个 .LIB 文件中,链接器在该文件中搜索包含所需符号的模块。静态链接的一个效果是只有程序使用的库中的那些模块才链接到它。其他模块被忽略。例如,传统的 C 数学库包含许多三角函数。但是,如果您链接它并使用
cos()
,您最终不会得到sin()
或tan()
的代码副本,除非您还调用了这些函数.对于具有丰富特性的大型库,选择性地包含模块很重要。在许多平台(例如嵌入式系统)上,库中可用的代码总大小可能比设备中存储可执行文件的可用空间大。如果没有选择性地包含,就很难管理为这些平台构建程序的细节。但是,在每个运行的程序中拥有 相同 库的副本会给通常运行大量进程的系统带来负担。使用正确的虚拟内存系统,具有相同内容的内存页只需要在系统中存在一次,但可以被许多进程使用。这为增加包含代码的页面可能与尽可能多的其他正在运行的进程中的某个页面相同的机会创造了一个好处。但是,如果程序静态链接到运行时库,那么每个程序都有不同的函数组合,每个函数都布置在不同位置的进程内存映射中,并且没有多少可共享的代码页,除非它是一个程序本身就是运行超过进程。因此,DLL 的想法获得了另一个主要优势。
库的 DLL 包含其所有功能,可供任何客户端程序使用。如果许多程序加载该 DLL,它们都可以共享其代码页。每个人都赢了。 (嗯,直到你用新版本更新一个 DLL,但这不是这个故事的一部分。谷歌 DLL Hell 是故事的那一面。)
所以在规划一个新项目时,首先要做的大选择是在动态和静态链接之间。使用静态链接,您需要安装的文件更少,并且您不会受到第三方更新您使用的 DLL 的影响。但是,您的程序更大,它不是 Windows 生态系统的好公民。使用动态链接,您需要安装更多文件,您可能会遇到第三方更新您使用的 DLL 的问题,但您通常对系统上的其他进程更友好。
DLL 的一大优点是无需重新编译甚至重新链接主程序即可加载和使用它。这可以允许第三方库提供商(例如 Microsoft 和 C 运行时)修复其库中的错误并分发它。一旦最终用户安装了更新后的 DLL,他们会立即在使用该 DLL 的所有程序中受益于该错误修复。 (除非它破坏了东西。参见 DLL Hell。)
另一个优点来自隐式加载和显式加载之间的区别。如果您进行显式加载的额外工作,那么在编写和发布程序时,DLL 甚至可能不存在。例如,这允许可以发现和加载插件的扩展机制。