导入库如何工作?细节?


87

我知道这对于极客来说似乎很基础。但我想使它变得清晰。

当我想使用Win32 DLL时,通常只调用LoadLibrary()和GetProcAdderss()之类的API。但是最近,我正在使用DirectX9进行开发,并且需要添加d3d9.libd3dx9.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导出方法的入口点的相对地址(或文件偏移量?)应在存根中进行编码;因此可以进行正确的跳转/方法调用。

我对吗?还有更多吗?

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


4
我发现没有人解决您问题的最后一部分,后者涉及可以检查导入库的工具。使用Visual C ++,至少有两种方法可以做到这一点:lib /list xxx.liblink /dump /linkermember xxx.lib。请参阅此堆栈溢出问题
艾伦(Alan)

此外,dumpbin -headers xxx.libliblink实用程序相比,还提供了一些更详细的信息。
m_katsifarakis

Answers:


100

链接到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的文章“深入了解Win32便携式可执行文件格式”非常全面地概述了EXE文件的格式以及如何加载和运行它。由于它最初出现在MSDN Magazine加利福尼亚州中,因此甚至对其进行了更新以涵盖.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为止,但这不属于本故事的一部分。故事的这一方面,Google DLL地狱。)

因此,在计划新项目时要做的第一大选择是在动态链接和静态链接之间。使用静态链接,可以安装的文件更少,并且可以避免第三方更新您使用的DLL。但是,您的程序较大,并且不是Windows生态系统的良好公民。使用动态链接,您需要安装更多文件,第三方更新您使用的DLL时可能会遇到问题,但是通常您对系统上的其他进程更加友好。

DLL的一大优点是,无需重新编译甚至不链接主程序就可以加载和使用它。这可以允许第三方库提供程序(例如,Microsoft和C运行时)修复其库中的错误并进行分发。最终用户安装更新的DLL后,他们会立即在使用该DLL的所有程序中获得该错误修复的好处。(除非它使事情中断。请参阅DLL Hell。)

另一个优点来自隐式加载和显式加载之间的区别。如果您要花大量精力进行显式加载,则在编写和发布程序时DLL可能甚至不存在。例如,这允许使用扩展机制来发现和加载插件。


3
删除我的帖子并予以支持,因为您向我解释的方式比我更好;)不错的答案。
ereOn 2010年

2
@RBerteig:感谢您的出色回答。根据此处的内容(msdn.microsoft.com/zh-cn/library/9yd93633.aspx),仅需进行一点校正,就可以动态地链接到DLL的两种类型:加载时隐式链接运行时显式链接。没有编译时链接。现在,我想知道传统的静态链接(链接到包含完整实现的* .lib文件)和加载时动态链接到DLL(通过导入库)之间有什么区别?
smwikipedia 2010年

1
继续:静态链接加载时动态链接的优缺点是什么?似乎这两种方法都在过程开始时将所有必需的文件加载到地址空间中。为什么我们需要其中2个?谢谢。
smwikipedia 2010年

1
您可能可以使用“ objdump”之类的工具来查看.lib文件,并确定它是导入库还是真正的静态库。在Linux上交叉编译为Windows目标时,可以在.a文件(.lib文件的Mingw版本)上运行'ar'或'nm',并且请注意,导入库具有通用的.o文件名,并且没有代码(只是一个“ jmp”指令),而静态库内部有很多功能和代码。
不要亮

1
小修正:您也可以在运行时隐式链接。延迟加载的DLL的链接器支持对此进行了详细说明。如果您要动态更改DLL搜索路径,或正常处理导入解析失败(以支持新的OS功能,但仍在旧版本上运行),这将很有帮助。
IInspectable'9

5

这些.LIB导入库文件在以下项目属性中使用Linker->Input->Additional Dependencies,当构建一堆需要在链接时由导入库.LIB文件提供的其他信息的dll时。在以下示例中,为了不出现链接器错误,我需要通过其lib文件引用dll的A,B,C和D。(请注意,链接器会找到这些文件,您可能需要在其中包含其部署路径Linker->General->Additional Library Directories否则您将收到关于无法找到任何提供的lib文件的构建错误。)

链接器->输入->其他依赖项

如果您的解决方案正在构建所有动态库,则您可以通过依赖于在 Common Properties->Framework and References对话框。这些标志似乎可以使用* .lib文件代表您自动进行链接。 框架和参考

但是,这就是说的“通用属性”,它不是配置或平台特定的。如果您需要像我们的应用程序中那样支持混合构建方案,则可以使用构建配置来呈现静态构建,并且可以使用特殊配置来构建作为动态库部署的部分程序集的受限构建。我用过Use Library Dependency InputsLink Library Dependencies在各种情况下 和标志设置为true来构建内容,后来又实现了简化目的,但是在将代码引入静态版本时,我引入了大量的链接器警告,对于静态版本而言,该版本的运行速度非常慢。我总结出了很多这样的警告。

warning LNK4006: "bool __cdecl XXX::YYY() already defined in CoreLibrary.lib(JSource.obj); second definition ignored  D.lib(JSource.obj)

我最后使用手动规范Additional Dependencies来满足动态构建的链接器,同时通过不使用会减慢其速度的公共属性来使静态构建器满意。部署动态子集构建时,我仅部署dll文件,因为这些lib文件仅在链接时使用,而不在运行时使用。


3

库分为三种:静态库,共享库和动态加载库。

静态库在链接阶段与代码链接在一起,因此它们实际上位于可执行文件中,这与共享库不同,共享库只有存根(符号)要在共享库文件中查找,共享库文件是在运行时加载的。主函数被调用。

动态加载的库与共享库非常相似,不同之处在于它们是在您编写的代码在需要时以及在需要时加载的。


@谢谢zacsek。但是我不确定您对共享库的声明。
smwikipedia 2010年

@smwikipedia:Linux拥有它们,我使用它们,因此它们绝对存在。另请阅读:en.wikipedia.org/wiki/Library_(computing)
佐尔坦Szőcs

3
这是一个微妙的区别。共享库和动态库都是DLL文件。区别在于它们何时加载。共享库由操作系统与EXE一起加载。动态库通过代码调用LoadLibrary()和相关的API加载。
RBerteig 2010年

从[1]中我读到DLL是Microsoft对共享库概念的实现。[1]:en.wikipedia.org/wiki/Dynamic-link_library#Import_libraries
smwikipedia 2010年

我不同意这是一个微妙的区别,从编程角度看,无论是否动态加载共享库(如果是动态加载,那么都必须添加样板代码来访问函数)会产生巨大的差异。
佐尔坦Szőcs

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.