【问题标题】:Why do we need .lib file in case of importing functions from .dll?如果从 .dll 导入函数,为什么我们需要 .lib 文件?
【发布时间】:2021-07-30 23:23:47
【问题描述】:

你能帮我理解一下,为什么我们在从 dll 中导入函数和数据时需要 .lib 文件吗?

我听说它包含了相应 dll 中导出的函数和数据元素的列表,但是当我使用 CFF Explorer 探索我的 dll 时,我发现 dll 已经有导出函数的地址,所以我理论上可以链接我的带有 .dll 的程序,无需任何附加文件。

您能否更详细地解释一下 .lib 文件中存储了哪些类型的数据。
而且,是的,我知道,Visual Studio 强制我们将 .lib 文件添加到附加依赖项部分,但为什么它真的需要它们?

【问题讨论】:

  • 合法问题。我也有兴趣
  • @MaxKunes 你现在严肃吗?这是一个完全不同的问题,请在发布前阅读它
  • @MaxKunes,我已经阅读了一些类似的帖子,所以它不能回答我的问题。它只是告诉我发生了什么,但没有答案为什么会发生。
  • 阅读linkers and loaders,Levine 的一本书。顺便说一句,Linux 和大多数其他 Unix 上的情况确实不同

标签: c++ visual-studio dll lib


【解决方案1】:

当您的源代码静态调用导出的 DLL 函数,或静态访问导出的 DLL 变量时,这些引用将作为指针编译到可执行文件的中间对象文件中,其值在运行时填充。

当链接器组合编译器生成的目标文件以生成最终的可执行文件时,它必须弄清楚所有编译器生成的引用实际上指的是什么。如果它无法匹配对可执行文件中某些代码的给定引用,则需要将其与外部 DLL 匹配。所以它需要知道要查看哪些 DLL,以及这些 DLL 如何导出内容。 DLL 可以通过名称或序号导出给定的函数/变量,因此链接器需要一种方法来将代码引用使用的标识符映射到特定 EXPORTS 文件中的特定条目(尤其是在按序数导出事物的情况)。静态链接.lib 文件为链接器提供该映射信息(即FunctionA 映射到DLL XYZ.dll 中的Ordinal 123FunctionB 映射到DLL _FunctionB@4 中的名称_FunctionB@4 等)。

然后,链接器可以使用有关所需的适当 EXPORTS 条目的信息填充可执行文件的 IMPORTS 表,然后使代码中的 DLL 引用指向正确的 IMPORTS 条目(如果链接器可以' t 解析编译器生成的对可执行文件中的一段代码或特定 DLL 导出的引用,它会因“未解决的外部”错误而中止。

然后,当您的可执行文件在运行时加载时,OS 加载程序会查看 IMPORTS 表以了解需要哪些 DLL 导出,因此它可以将适当的 DLL 加载到内存中并更新 @ 中的条目987654334@ 表具有基于每个 DLL 的EXPORTS 表的实际内存地址(如果引用的 DLL 无法加载,或者如果找不到引用的导出,OS Loader 将中止加载您的可执行文件)。这样,当您的代码调用 DLL 函数或访问 DLL 变量时,这些访问会转到正确的位置。

如果您的源代码动态通过在运行时显式调用GetProcAddress() 访问DLL 函数/变量,情况就会大不相同。在这种情况下,这些访问不需要静态链接 .lib 文件,因为您自己的代码正在处理将 DLL 加载到内存中并定位它想要使用的导出。

但是,还有第三个选项可以将上述场景混合在一起:您可以编写代码以静态地访问 DLL 函数/变量,但使用链接器的 延迟加载 功能(如果有的话)。在这种情况下,您仍然需要为您访问的每个延迟加载的 DLL 提供静态链接 .lib 文件,但链接器会在可执行文件中使用对 DLL 导出的引用填充单独的 DELAYLOAD 表,而不是填充 IMPORTS桌子。它将编译器生成的 DLL 引用指向编译器 RTL 中的存根,当在运行时第一次访问存根时,它将用来自GetProcAddress() 的地址替换引用,从而避免需要由加载时的 OS 加载程序。这使您的可执行文件即使在加载时不存在 DLL 导出也可以正常运行,并且如果它们从未使用过,甚至可能根本不需要加载 DLL(当然,如果您的可执行文件确实尝试访问 DLL 导出动态加载并且无法加载,您的代码可能会崩溃,但这是一个单独的问题)。

【讨论】:

  • 我知道我们需要 .lib 文件中的这些地址,但为什么我们不能从 .dll 中找出这些地址。它不包含相同的地址吗?因为处理更多的依赖项有点开销,不是吗?
  • @LexMarchenko 我用更多信息更新了我的答案。链接器不能只查看 DLL。首先,它需要知道要查看哪些 DLL,然后需要知道具体的导出是如何定义的。 .lib 文件提供此类映射信息。它们根本不包含实际地址。
  • 归根结底,我在运行我的应用程序时使用 dll,那么为什么我们首先需要那个 lib
  • @pujadeo 我在回答中提到了这一点。见第 2 段和第 6 段。静态链接导入 .lib 文件为 链接器 提供了它在确定可执行文件将在运行时使用哪些 DLL 的哪些导出时所需的信息。
  • @pujadeo static-link import .lib 文件对应于 DLL,.obj 文件对应于源代码文件。它们只是预编译的代码/元数据,链接器可以在每次重新编译最终可执行文件时反复重用。如果源文件/DLL 发生更改,它的 obj/lib 也会更新。
【解决方案2】:

我听说它包含了从相应dll导出的函数和数据元素的列表,但是当我使用CFF Explorer探索我的dll时,我发现dll已经有导出函数的地址所以我理论上无需任何附加文件即可将我的程序与 .dll 链接。

作为为什么这不能总是有效的一个简单示例,考虑一个访问两个 DLL 的可执行文件,一个用于 Winsock 过滤器,另一个用于分配器。假设在这台特定的机器上,Winsock 过滤器 DLL 恰好也实现了具有相同 API 的分配器,而分配器 DLL 恰好也实现了具有相同 API 的 Winsock 过滤器。编译器如何知道从哪个 DLL 访问哪些 API 函数?库文件包含了访问DLL的intent,也就是你要访问的API和函数。

重要的是,没有“相应的 DLL”之类的东西。不同系统上可能有不同的 DLL 文件。链接器需要知道的是它可以依赖的 DLL 应该是什么样子,而不是您可能碰巧在某些特定系统上使用的 DLL 可能碰巧是什么。

例如,假设 DLL 文件包含一个分配器。您可能有一个用于调试的分配器的 DLL 文件,一个用于针对特定 CPU 版本进行优化的分配器,以及一个用于使用新的实验性算法的分配器。链接器需要知道的是所有这些 DLL 文件实现的 API,而不是任何一个文件中的具体实现。

您可以produce a LIB file from a DLL file,但您最终可能会构建一个在使用其他版本的 DLL 文件时无法运行的可执行文件。您将不得不假设,无论这个特定的 DLL 碰巧做什么,都恰好是实现相同 API 的所有其他 DLL 碰巧做的事情。

【讨论】:

  • 所以,我假设,我们实际上并不需要一个 .lib 文件,但是用它来构建更方便,因为我们可以确定具有给定原型的函数实际上存在于dll。如果没有,我们可以生成编译时错误而不是运行时错误。我说的对吗?
  • 我们确实需要一个 .lib 文件。否则,编译器如何从 which DLL 中知道要使用的 which 函数?我认为您错过了使用 DLL 的程序正在使用 DLL 实现的某些特定 API 并且任何数量的其他 DLL 文件可以以不同方式实现相同 API 的观点。没有“一个正确的 DLL 文件”会一直被使用。例如,对于系统 DLL,新版本的操作系统将具有这些 DLL 的新版本,这些新版本可能在根本上有所不同,但实现了相同的 API。 LIB 文件记录了 API。
  • @DavidSchwartz 我正在使用的代码库同时生成 dll 和 lib 文件。但是我的软件只使用 dll 文件。那为什么我们首先要生成库呢?
  • @pujadeo application 不使用 lib 文件,linker 在编译应用程序时使用 lib 文件。请再次仔细阅读答案,您似乎遗漏了其中的关键信息。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-04-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多