如果从 .dll 导入函数,为什么需要 .lib 文件?

2021-12-25 00:00:00 visual-studio dll lib c++

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

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

请您更详细地解释.lib 文件中存储的数据类型.
而且,是的,我知道,visual studio 强制我们将 .lib 文件添加到附加依赖项部分,但为什么它真的需要它们?

Can you help me to understand, why do we need .lib files when importing functions and data from dll?

I've heard, that it contains a list of the exported functions and data elements from the corresponding dll, but when I used CFF Explorer to explore my dll, I found out that dll already has addresses of exporting functions so I theoretically can link my program with .dll without any additional files.

Can you, please, explain what kind of data is stored in the .lib files more detailed.
And, also, yes, I know, that visual studio forces us to add .lib files into additional dependencies section, but why does it really needs them?

推荐答案

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

When your source code statically calls exported DLL functions, or statically accesses exported DLL variables, those references are compiled into your executable's intermediate object files as pointers, whose values get populated at run-time.

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

When the linker is combining the compiler-generated object files to make the final executable, it has to figure out what all of the compiler-generated references actually refer to. If it can't match a given reference to some piece of code in your executable, it needs to match it to an external DLL instead. So it needs to know which DLLs to even look at, and how those DLLs export things. A DLL may export a given function/variable by name OR by ordinal number, so the linker needs a way to map the identifiers used by your code references to specific entries in the EXPORTS tables of specific .dll files (especially in the case where things are exported by ordinals). Static-link .lib files provide the linker with that mapping information (ie FunctionA maps to Ordinal 123 in DLL XYZ.dll, FunctionB maps to name _FunctionB@4 in DLL ABC.dll, etc).

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

The linker can then populate the IMPORTS table of your executable with information about the appropriate EXPORTS entries needed, and then make the DLL references in your code point to the correct IMPORTS entries (if the linker can't resolve a compiler-generate reference to a piece of code in your executable, or to a specific DLL export, it aborts with an "unresolved external" error).

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

Then, when your executable is loaded at run-time, the OS Loader looks at the IMPORTS table to know which DLL exports are needed, so it can then load the appropriate DLLs into memory and update the entries in the IMPORTS table with real memory addresses that are based on each DLL's EXPORTS table (if a referenced DLL fails to load, or if a referenced export fails to be found, the OS Loader aborts loading your executable). That way, when your code calls DLL functions or accesses DLL variables, those accesses go to the right places.

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

Things are very different if your source code dynamically accesses DLL functions/variables via explicit calls to GetProcAddress() at run-time. In that case, static-link .lib files are not needed for those accesses, since your own code is handling the loading of DLLs into memory and locating the exports that it wants to use.

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

However, there is a 3rd option that blends the above scenarios together: you can write your code to access the DLL functions/variables statically but use your linker's delay-load feature (if it has one). In that case, you still need static-link .lib files for each delay-loaded DLL you access, but the linker populates a separate DELAYLOAD table in your executable with references to the DLL exports, instead of populating the IMPORTS table. It points the compiler-generated DLL references to stubs in your compiler's RTL that will replace the references with addresses from GetProcAddress() when the stubs are accessed for the first time at run-time, thus avoiding the need for the references to be populated by the OS Loader at load-time. This allows your executable to run normally even if the DLL exports are not present at load-time, and may not even need to load the DLLs at all if they are never used (of course, if your executable does try to access a DLL export dynamically and it fails to load, your code is likely to crash, but that is a separate issue).

相关文章