导入库如何工作?细节?

smw*_*dia 76 c c++ windows visual-c++

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

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

我听说LIB用于静态链接,DLL用于动态链接.

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

但有时候,DLL文件会附带一些LIB 文件,因此:

  • 这些LIB文件是什么?
  • 他们如何实现他们的意图?
  • 有没有工具可以让我检查这些LIB文件的内部?

更新1

检查维基百科后,我记得这些LIB文件称为导入库.但我想知道它如何与我的主应用程序和动态加载的DLL一起工作.

更新2

正如RBerteig所说,LIB文件中存在一些与DLL一起出现的存根代码.所以调用序列应该是这样的:

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

那么这些LIB应包含哪些信息?我能想到以下几点:

  • LIB文件应包含相应DLL的完整路径; 因此DLL可以由运行时加载.
  • 每个DLL导出方法的入口点的相对地址(或文件偏移量?)应该在存根中编码; 因此可以进行正确的跳转/方法调用.

我对吗?还有什么吗?

顺便说一句:有没有可以检查导入库的工具?如果我能看到它,就不会再有疑惑了.

RBe*_*eig 90

链接到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文件格式及其加载和运行方式的完整概述.它甚至已经更新,以涵盖.NET和更多,因为它最初出现在MSDN杂志ca. 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地狱的故事的那一面.)

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

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

另一个优点来自隐式加载和显式加载之间的区别.如果你需要额外的显式加载工作,那么在编写和发布程序时,DLL可能甚至不存在.例如,这允许可以发现和加载插件的扩展机制.

  • 删除我的帖子并取消这一点,因为你比我更好地解释事情;)很好的答案. (3认同)
  • @RBerteig:谢谢你的回答.根据这里(http://msdn.microsoft.com/en-us/library/9yd93633.aspx),只有一点修正,有两种类型的动态链接到DLL,*加载时隐式链接*和*运行时显式链接*.没有*编译时链接*.现在我想知道传统的*Static Linking*(链接到包含完整实现的\*.lib文件)和*Load-Time动态链接*到DLL(通过导入库)之间的区别是什么? (2认同)

jxr*_*mos 6

这些 .LIB 导入库文件在以下项目属性中使用Linker->Input->Additional Dependencies,当构建一堆 dll 时,这些 dll 在链接时需要由导入库 .LIB 文件提供的附加信息。在下面的示例中,为了避免链接器错误,我需要通过它们的 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)
Run Code Online (Sandbox Code Playgroud)

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