c++ - 导入库如何工作?详情?

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

我知道这对极客来说似乎很基本。但我想说清楚。

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

我已经听够了 LIB 用于静态链接而 DLL 用于动态链接。

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

但有时,有一些 LIB 文件 随附 DLL 文件,因此:

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

  • 更新 1

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

    更新 2

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

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

    那么这些LIB中应该包含哪些信息呢?我可以想到以下几点:
  • LIB 文件应包含相应 DLL 的完整路径;因此 DLL 可以由运行时加载。
  • 每个 DLL 导出方法的入口点的相对地址(或文件偏移量?)应该在 stub 中编码;因此可以进行正确的跳转/方法调用。

  • 我是对的吗?还有什么吗?

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

    最佳答案

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

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

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

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

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

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

    更新

    如果您无法抗拒了解所有具体细节和实际情况,那么 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 甚至可能不存在。例如,这允许可以发现和加载插件的扩展机制。

    关于c++ - 导入库如何工作?详情?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3573475/

    相关文章:

    c++ - C++ 中的指针初始化(或缺少)

    c - 使用 fork 显示循环中的进程数

    python - Cython 静态链接与 python 运行时?

    windows - Powershell 排序和过滤

    c++ - 搜索相似对象

    c++ - Constexpr 值作为指针类型的非类型模板参数

    支持 .h .cpp 同步的 C++ 源代码文本编辑器

    控制管道 StdIn 和 StdOut

    C泛型类型作为函数参数输入

    CreateFileMapping() 用于将长度未知的文本写入文件