导入库如何工作? 细节?
我知道这可能是非常基本的怪才。 但是我想把它弄清楚。
当我想要使用Win32 DLL时,通常我只是调用LoadLibrary()和GetProcAdderss()之类的API。 但是最近,我正在用DirectX9开发,我需要添加d3d9.lib , d3dx9.lib等文件。
我已经听够了,LIB是用于静态链接的,DLL是用于dynamic链接的。
所以我目前的理解是,LIB包含方法的实现,并在链接时作为最终EXE文件的一部分静态链接。 DLL是在运行时dynamic加载的,不是最终EXE文件的一部分。
但是有时候,DLL文件会附带一些LIB文件,所以:
- 这些LIB文件是什么?
- 他们如何达到他们的目的?
- 有没有什么工具可以让我检查这些LIB文件的内部?
更新1
检查维基百科后,我记得这些LIB文件被称为导入库 。 但我想知道它是如何与我的主要应用程序和DLLdynamic加载。
更新2
就像RBerteig所说的那样,DLL中生成的LIB文件中有一些存根代码。 所以调用顺序应该是这样的:
我的主要应用程序 – >存根在LIB – >真正的目标DLL
那么这些LIB应该包含哪些信息呢? 我可以想到以下几点:
- LIB文件应该包含相应DLL的完整path; 所以DLL可以被运行时加载。
- 每个DLL导出方法入口点的相对地址(或文件偏移量?)应编码到存根中; 因此,可以进行正确的跳转/方法调用。
我对吗? 还有更多吗?
顺便说一句:有什么工具可以检查导入库吗? 如果我能看到它,就不会有任何怀疑。
链接到一个DLL文件可以隐式地在编译链接时发生,或者在运行时显式地发生。 无论哪种方式,该DLL最终加载到进程内存空间,并且它的所有输出入口点都可用于应用程序。
如果在运行时显式使用,则使用LoadLibrary()
和GetProcAddress()
手动加载DLL并获取指向您需要调用的函数的指针。
如果在构build程序时隐式链接,那么程序使用的每个DLL导出的存根将从导入库链接到程序中,并且在进程启动时加载EXE和DLL,这些存根会更新。 (是的,我在这里简化了一些…)
这些存根需要来自某个地方,而在Microsoft工具链中,它们来自一个名为导入库的特殊forms的.LIB文件。 所需的.LIB通常与DLL同时生成,并且包含从DLL导出的每个函数的存根。
令人困惑的是,同一个库的一个静态版本也将作为一个.LIB文件提供。 除了LIB是用于DLL的导入库通常比匹配的静态LIB更小(通常小得多)之外,没有简单的方法来区分它们。
如果您使用GCC工具链,顺便说一句,您实际上并不需要导入库来匹配您的DLL。 移植到Windows的Gnu链接器的版本可以直接理解DLL,并且可以dynamic合成大多数所需的存根(stub)。
更新
如果你无法知道所有的细节和实际情况,那么MSDN总会有一些帮助。 Matt Pietrek的文章“深入研究Win32可移植可执行文件格式”是对EXE文件格式及其如何加载和运行的完整概述。 它甚至已经更新,涵盖.NET和更多,因为它最初出现在MSDN杂志约。 2002年。
另外,知道如何准确了解某个程序使用了哪些DLL是有帮助的。 这个工具是Dependency Walker,也叫depend.exe。 它的一个版本包含在Visual Studio中,但最新的版本可以从http://www.dependencywalker.com/的作者处获得。; 它可以识别链接时指定的所有DLL(包括早期加载和延迟加载),也可以运行程序并监视运行时加载的任何其他DLL。
更新2
我已经重新编写了一些早期的文本,以便在重新阅读时予以澄清,并使用隐式和显式链接术语来与MSDN保持一致。
所以,我们有三种方法可以让程序使用库函数。 那么明显的后续问题是:“如何select哪条路?
静态链接是程序本身的大部分链接。 所有的目标文件都被列出,并被链接器一起收集到EXE文件中。 一路上,链接器会照顾小事务,比如修正对全局符号的引用,以便模块可以调用彼此的函数。 库也可以静态链接。 组成该库的目标文件由一个库pipe理员收集在一个.LIB文件中,链接器search包含所需符号的模块。 静态链接的一个效果是,只有程序使用的库中的那些模块才与之链接; 其他模块被忽略。 例如,传统的Cmath库包含许多三angular函数。 但是,如果你连接它并使用cos()
,那么除非你也调用了这些函数,否则不会得到sin()
或tan()
的代码的副本。 对于具有丰富function的大型图书馆而言,select性地包含模块是非常重要的。 在诸如embedded式系统的许多平台上,与可用于在设备中存储可执行文件的空间相比,可用于库中的代码的总大小可以是大的。 如果没有select性纳入,那么pipe理这些平台的构build计划的细节将会变得更加困难。
但是,在每个运行的程序中都有一个相同的库的副本会对通常运行大量进程的系统造成负担。 使用正确的虚拟内存系统,具有相同内容的内存页面只需要在系统中存在一次,但是可以被多个进程使用。 这为增加包含代码的页面可能与其他尽可能多的正在运行的进程中的某个页面相同的机会创造了好处。 但是,如果程序静态地链接到运行时库,那么每个程序在不同位置的进程内存映射中都有不同的function组合,除非它本身是一个程序,否则不存在许多可共享的代码页运行在多个进程中。 所以,DLL的想法获得了另一个重要的优势。
库的一个DLL包含了它的所有function,可供任何客户端程序使用。 如果许多程序加载该DLL,它们都可以共享其代码页。 每个人都赢了。 (好吧,直到你更新了一个新版本的DLL,但这不是这个故事的一部分,Google DLL就是这个故事的一部分。)
所以在规划一个新项目时,第一个大的select就是dynamic和静态的联系。 使用静态链接,您可以安装更less的文件,而且您可以免受第三方更新您使用的DLL的影响。 但是,你的程序比较大,并不是Windows生态系统的好公民。 通过dynamic链接,您可以安装更多的文件,您可能会遇到第三方更新您使用的DLL的问题,但您通常对系统上的其他进程更友善。
DLL的一大优点是可以在不重新编译甚至重新链接主程序的情况下加载和使用它。 这可以允许第三方库提供者(比如微软和C运行库)修复库中的一个bug并分发它。 一旦最终用户安装更新后的DLL,他们立即从所有使用该DLL的程序中获得该错误修复的好处。 (除非它破坏了东西,看DLL地狱。)
另一个优点来自隐式和显式加载之间的区别。 如果你花费额外的精力来显式加载,那么当程序被编写和发布时,DLL甚至可能不存在。 例如,这允许可以发现和加载插件的扩展机制。
有三种库:静态,共享和dynamic加载库。
静态库在链接阶段与代码链接,所以它们实际上在可执行文件中,不同于共享库,共享库只有存根(符号)在共享库文件中查找,而共享库文件在运行时主函数被调用。
dynamic加载的类似于共享库,除非它们在您编写的代码需要时加载。
这里有一些相关的MSDN主题来回答我的问题:
将可执行文件链接到DLL
隐含链接
确定使用哪种链接方法
构build导入库和导出文件
这些.LIB导入库文件在下面的项目属性Linker->Input->Additional Dependencies
,当在链接时生成需要附加信息的一堆dll时,这是由导入库.LIB文件提供的。 在下面的例子中,不要获取链接器错误,我需要通过它们的lib文件引用dll的A,B,C和D. (注意链接器find这些文件,你可能需要在链接器 – Linker->General->Additional Library Directories
包含它们的部署path,否则你会得到一个关于无法find任何提供的lib文件的构build错误。
如果您的解决scheme正在构build所有dynamic库,则可以通过依靠Common Properties->Framework and References
对话框中公开的引用标志来避免显式依赖规范。 这些标志似乎自动使用* .lib文件代表您的链接。
然而,正如它所说的通用属性,这不是configuration或平台特定的。 如果您需要像我们的应用程序一样支持混合构buildscheme,那么我们有一个构buildconfiguration来呈现一个静态构build和一个特殊的configuration,构build了部署为dynamic库的部分子集的约束构build。 我曾经使用过Use Library Dependency Inputs
和Link Library Dependencies
标志在各种情况下设置为true来构build事物并稍后实现以简化事情,但是当将我的代码引入静态构build时,我引入了大量的链接器警告,构build是令人难以置信的慢静态构build。 我结束了介绍一堆这样的警告…
warning LNK4006: "bool __cdecl XXX::YYY() already defined in CoreLibrary.lib(JSource.obj); second definition ignored D.lib(JSource.obj)
最后,我使用Additional Dependencies
的手动规范来满足dynamic构build的链接器,同时通过不使用减慢静态构build器的公共属性来保持静态构build器的快乐。 当我部署dynamic子集版本时,我只部署dll文件,因为这些lib文件仅在链接时使用,而不是在运行时使用。