1、链接库概念
静态链接库和动态链接库介绍
我们可以创建一种文件里面包含了很多函数和变量的目标代码,链接的时候只要把这个文件指示给链接程序就自动地从文件中查找符合要求的函数和变量进行链接,整个查找过程根本不需要我们操心。
这个文件叫做 “库(Libary)”,平时我们把编译好的目标代码存储到“库”里面,要用的时候链接程序帮我们从库里面找出来。
静态链接库:
在早期库的组织形式相对简单,里面的目标代码只能够进行静态链接,所以我们称为“静态库”,静态库的结构比较简单,其实就是把原来的目标代码放在一起,链接程序根据每一份目标代码的符号表查找相应的符号(函数和变量的名字),找到的话就把该函数里面需要定位的进行定位,然后将整块函数代码放进可执行文件里,若是找不到需要的函数就报错退出。
静态库的两个特点:
1、链接后产生的可执行文件包含了所有需要调用的函数的代码,因此占用磁盘空间较大。
2、如果有多个(调用相同库函数的)进程在内存中同时运行,内存中就存有多份相同的库函数代码,因此占用内存空间较多。
动态链接库:
动态链接库就是为了解决这些问题而诞生的技术,顾名思义,动态链接的意思就是在程序装载内存的时候才真正的把库函数代码链接进行确定它们的地址,并且就算有几个程序同时运行,内存也只存在一份函数代码。
动态库的代码必须满足这样一种条件:能够被加载到不同进程的不同地址,所以代码要经过特别的编译处理,我们把这种经过特别处理的代码叫做“位置无关代码(Position independed Code .PIC)”.
根据载入程序何时确定动态代码的逻辑地址,可以把动态装载分为两类。
1、静态绑定(static binding)
使用静态绑定的程序一开始载入内存的时候,载入程序就会把程序所有调用到的动态代码的地址算出确定下来,这种方式使程序刚运行的初始化时间较长,不过旦完成动态装载,程序的运行速度就很快。
2、动态绑定(dynamic binding)
使用这种方式的程序并不在一开始就完成动态链接,而是直到真正调用动态库代码时,载入程序才计算(被调用的那部分)动态代码的逻辑地址,然后等到某个时候,程序又需要调用另外某块动态代码时,载入程序又去计算这部分代码的逻辑地址,所以,这种方式使程序初始化时间较短,但运行期间的性能比不上静态绑定的程序。
平时默认进行链接的标准 C/C++ 函数就是动态库。
2、链接库常识
目前以lib后缀的库有两种,一种为静态链接库(Static Libary,以下简称“静态库”),另一种为动态连接库(DLL,以下简称“动态库”)的导入库(Import Libary,以下简称“导入库”)。
静态库是一个或者多个obj文件的打包,所以有人干脆把从obj文件生成lib的过程称为Archive,即合并到一起。比如你链接一个静态库,如果其中有错,它会准确的找到是哪个obj有错,即静态lib只是壳子。
动态库一般会有对应的导入库,方便程序静态载入动态链接库,否则你可能就需要自己LoadLibary调入DLL文件,然后再手工GetProcAddress获得对应函数了。有了导入库,你只需要链接导入库后按照头文件函数接口的声明调用函数就可以了。
导入库和静态库的区别很大,他们实质是不一样的东西。静态库本身就包含了实际执行代码、符号表等等,而对于导入库而言,其实际的执行代码位于动态库中,导入库只包含了地址符号表等,确保程序找到对应函数的一些基本地址信息。 这也是实际上很多开源代码发布的惯用方式:
1. 预编译的开发包:包含一些.dll文件和一些.lib文件。其中这里的.lib就是导入库,而不要错以为是静态库。但是引入方式和静态库一样,要在链接路径上添加找到这些.lib的路径。而.dll则最好放到最后产生的应用程序exe执行文件相同的目录。这样运行时,就会自动调入动态链接库。
2. 用户自己编译: 下载的是源代码,按照readme自己编译。生成很可能也是.dll + .lib(导入库)的库文件
3. 如果你只有dll,并且你知道dll中函数的函数原型,那么你可以直接在自己程序中使用LoadLibary调入DLL文件,GetProcAddress
DLL:
动态链接库 (DLL) 是作为共享函数库的可执行文件。动态链接提供了一种方法,使进程可以调用不属于其可执行代码的函数。函数的可执行代码位于一个 DLL 中,该 DLL 包含一个或多个已被编译、链接并与使用它们的进程分开存储的函数。DLL 还有助于共享数据和资源。多个应用程序可同时访问内存中单个 DLL 副本的内容。
动态链接与静态链接的不同之处在于它允许可执行模块(.dll 文件或 .exe 文件)仅包含在运行时定位 DLL 函数的可执行代码所需的信息。在静态链接中,链接器从静态链接库获取所有被引用的函数,并将库同代码一起放到可执行文件中。
使用动态链接代替静态链接有若干优点。扩展了应用程序的特性、、可以用许多种编程语言来编写、简化了软件项目的管理、有助于节省内存、有助于资源共享、有助于应用程序的本地化、有助于解决平台差异、可以用于一些特殊的目的。windows使得某些特性只能为DLL所用。
3、动态链接库的调用
有两种类型的链接:隐式链接和显式链接。
隐式链接
应用程序的代码调用导出 DLL 函数时发生隐式链接。 当调用可执行文件的源代码被编译或被汇编时,DLL 函数调用在对象代码中生成一个外部函数引用。 若要解析此外部引用,应用程序必须与 DLL 的创建者所提供的导入库(.LIB 文件)链接。
导入库仅包含加载 DLL 的代码和实现 DLL 函数调用的代码。 在导入库中找到外部函数后,会通知链接器此函数的代码在 DLL 中。 要解析对 DLL 的外部引用,链接器只需向可执行文件中添加信息,通知系统在进程启动时应在何处查找 DLL 代码。
系统启动包含动态链接引用的程序时,它使用程序的可执行文件中的信息定位所需的 DLL。 如果系统无法定位 DLL,它将终止进程并显示一个对话框来报告错误。 否则,系统将 DLL 模块映射到进程的地址空间中。
如果任何 DLL 具有(用于初始化代码和终止代码的)入口点函数,操作系统将调用此函数。 在传递到入口点函数的参数中,有一个指定用以指示 DLL 正在附带到进程的代码。 如果入口点函数没有返回 TRUE,系统将终止进程并报告错误。
最后,系统修改进程的可执行代码以提供 DLL 函数的起始地址。
与程序代码的其余部分一样,DLL 代码在进程启动时映射到进程的地址空间中,且仅当需要时才加载到内存中。 因此,由 .def 文件用来在 Windows 的早期版本中控制加载的 PRELOAD 和 LOADONCALL 代码特性不再具有任何意义。
显式链接
大部分应用程序使用隐式链接,因为这是最易于使用的链接方法。 但是有时也需要显式链接。 下面是一些使用显式链接的常见原因:
直到运行时,应用程序才知道需要加载的 DLL 的名称。 例如,应用程序可能需要从配置文件获取 DLL 的名称和导出函数名。
如果在进程启动时未找到 DLL,操作系统将终止使用隐式链接的进程。 同样是在此情况下,使用显式链接的进程则不会被终止,并可以尝试从错误中恢复。 例如,进程可通知用户所发生的错误,并让用户指定 DLL 的其他路径。
如果使用隐式链接的进程所链接到的 DLL 中有任何 DLL 具有失败的 DllMain 函数,该进程也会被终止。 同样是在此情况下,使用显式链接的进程则不会被终止。
因为 Windows 在应用程序加载时加载所有的 DLL,故隐式链接到许多 DLL 的应用程序启动起来会比较慢。 为提高启动性能,应用程序可隐式链接到那些加载后立即需要的 DLL,并等到在需要时显式链接到其他 DLL。
显式链接下不需将应用程序与导入库链接。 如果 DLL 中的更改导致导出序号更改,使用显式链接的应用程序不需重新链接(假设它们是用函数名而不是序号值调用 GetProcAddress),而使用隐式链接的应用程序必须重新链接到新的导入库。
下面是需要注意的显式链接的两个缺点:
如果 DLL 具有 DllMain 入口点函数,则操作系统在调用 LoadLibrary 的线程上下文中调用此函数。 如果由于以前调用了 LoadLibrary 但没有相应地调用 FreeLibrary 函数而导致 DLL 已经附加到进程,则不会调用此入口点函数。 如果 DLL 使用 DllMain 函数为进程的每个线程执行初始化,显式链接会造成问题,因为调用 LoadLibrary(或 AfxLoadLibrary)时存在的线程将不会初始化。
如果 DLL 将静态作用域数据声明为 __declspec(thread),则在显式链接时 DLL 会导致保护错误。 用 LoadLibrary 加载 DLL 后,每当代码引用此数据时 DLL 就会导致保护错误。 (静态作用域数据既包括全局静态项,也包括局部静态项。)因此,创建 DLL 时应避免使用线程本地存储区,或者应(在用户尝试动态加载时)告诉 DLL 用户潜在的缺陷。
4、显示链接和隐式链接的区别
一、Implicit Linking(隐式连接)
Implicit Linking(隐式连接) ,又叫静态载入,所谓静态载入是指程序在连接时期即与dlls所对应的import libraries作静态连接,于是可执行文件中便对所有的dll函数都有一份重定位表格(relocation table)和待修正记录(fixup record)。当程序被windows载入器载入内存中时,载入器会自动修正所有的fixup records,而这个fixup records 就是记录DLL中所有输出资源的正确位置地址,经过这样的程序动态连接便自动产生。也就是说,程序开始执行时,会用静态载入的方式时所使用的DLLs都载入到程序的内存里。
静态载入方式的优点
1、静态载入方式所使用的dll会在应用程序执行时载入,然后就可以调用所有dll中提供的函数,就像是程序中一样。
2、处理简单,载入的方法有编译器负责处理,不需动脑筋。
静态载入方式的缺点
1、当程序机构态载入方式所使用的dll不存在时,程序开始就会报dll无法找到的错误而使得程序无法运行。
编译时需要加入import library。
2、若调用的dll很多,载入应用程序的速度就会很慢。
不同的c++编译器静态载入的方式也不一样。
二、Explicit Linking(显式连接)
所谓Explicit Link(显式连接)又叫动态载入,使用dll的可执行文件必须明确调用载入和御载dll的函数调用(Function Call),并且存取dll的输出函数。用户端必须通过函数声明调用函数。
可执行文件可以使用任何一种连接方式的相同低dll。并且,这些机制之间并不会相互排斥,因此,当一个可执行文件隐式的连接dll时,其他程序还可以显示地连接它。
动态载入方式的优缺点:
1、dll只有需要时才载入内存中,这样可以更有效地使用存储空间。
2、应用程序载入速度较隐式连接较快,因为当程序开始载入时并不需要把dll载入到程序中。
3、编译时不需要额外的import library。
4、可以让用户个清楚地知道dll的载入流程。
缺点就是必须多写一点代码。
动态载入基本流程
必须使用LoadLibrary这个Windows API来手动载入DLL,并使用GetProcessAddress来取得所需要使用的函数的函数指针,最后用FreeLibrary将DLL释放。所以学会动态载入DLL时,必须先知道函数指针的用法。