链接到 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
我已经改写了一些较早的文本以在重新阅读时对其进行澄清,并使用艺术术语 implicit 和 explicit 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 甚至可能不存在。例如,这允许可以发现和加载插件的扩展机制。