ldd 命令
linux 下有动态库和静态库,动态库以.so为扩展名,静态库以.a为扩展名,查看某个程序使用了那些动态库,使用ldd命令查看。共享库是一个目标模块,在运行或加载时,可以加载到任意的内存地址,并和一个在内存中的程序链接起来。这个过程叫做动态链接,是由一个叫做动态链接器的程序来执行的。
windows 下的隐式调用
隐式调用,load time dynamic linking,就是说你的代码里面已经直接调用了库里面的函数,那么在 link 的时候会把该库的一小段 lib link 进去(而Linux上直接链接.so即可),这里面包含了这个DLL的相关信息以便在真正运行时能找到那个dll。然后当你exe运行时,windows 就会根据那些信息把需要用到的dll载入内存。
windows 下使用动态库,编译后往往提供两个文件:一个引入库(.lib)文件(也称“导入库文件”)和一个DLL(.dll)文件。windows 下隐式连接需要三个东西,分别是 .h头文件,lib库(动态的),DLL库。显式连接只需要.dll文件即可。程序会通过 lib 库,在编译阶段将调用动态库函数的代码之前对*加上动态库进行加载入内存的代码。
windows 下的显式调用
显式调用,run time dynamic linking,在编译以及 link 的时候是并不需要提供这个库的信息的。在程序真正运行的时候,通过自己调用 LoadLibrary, GetProcAddress 等 API 手工把 DLL 载入内存并找到里面的函数来调用。显示调用不会被 ldd 命令看到。
隐式链接虽然实现较简单,但除了必须的.dll文件外还需要DLL的.h文件和.lib文件,在那些只提供.dll文件的场合就无法使用,而只能采用显式链接的方式。这种方式通过调用API函数来完成对DLL的加载与卸载,能更加有效地使用内存,在编写大型应用程序时往往采用此方式。
一:显示链接
调用的 DLL的主工程的 main文件中代码如下:
#include <stdio.h>
#include <Windows.h>
#include <tchar.h>
int main()
{
HMODULE hModule = NULL;
typedef int (*Func)(int a, int b);
// 动态加载 DLL 文件
hModule = LoadLibrary(_TEXT("..//Debug//FuncDll.dll" ));
// 获取 add 函数地址
Func fAdd = (Func)GetProcAddress(hModule, "add" );
// 使用函数指针
printf("%d/n" , fAdd(5, 2));
// 最后记得要释放指针
FreeLibrary(hModule);
return 0;
}
二:隐式链接:
调用的 DLL的主工程的 main文件中代码如下:
#include <stdio.h>
#include <Windows.h>
#include <tchar.h>
// 先把 lib 链接进来
#pragma comment (lib , "..//Debug//FuncDll.lib" )
// 外部声明的 add 函数
extern "C" _declspec (dllimport )
int add(int a, char b);
int main()
{
// 直接调用 add 函数
printf("%d/n" , add(5, 2));
return 0;
}
Linux 下的隐式调用
动态库编译后的 .so 文件链接的时候需要这个.so,链接后编译完成之后,当这个程序运行的时候 .so 也是要必须在场的。(在linux中,例如用LD_LIBRARY_PATH环境变量设置动态链接库的查找地址),要不然程序会找不到这个.so而无法运行。LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib cmake_examples_inst_bin
Linux 下的显式调用
dlopenAPI 使得程序能在运行时打开一个共享库,根据名字在库中搜索一个函数并调用(linux/Unix系统编程手册)。
其中主要的函数是:
dlopen(libname,flag)
:打开 libname 共享库,flag 用来设置是否解析库中的未定义符号。如果打开的库还依赖于其他库,那么会递归加载对应的那些库dlerror()
:如果dlopen()函数调用出错,则用此函数能返回指向错误原因字符串的指针。‘dlclose(libname)
:卸载库。
动态链接小结
当把可执行文件复制到内存后,且在程序开始运行之前,操作系统会查找可执行文件依赖的动态库信息(主要是动态库的名字以及存放路径),找到该动态库后就将该动态库从磁盘搬到内存,并进行符号决议,如果这个过程没有问题,那么一切准备工作就绪,程序就可以开始执行了,如果找不到相应的动态库或者符号决议失败,那么会有相应的错误信息报告为用户,程序运行失败。
加载时动态链接可以分为两个阶段:阶段一,将动态库信息写入可执行文件(链接阶段);阶段二,加载可执行文件时依据动态库信息进行动态链接(加载阶段)。运行时动态链接这种方式对于“动态链接”阐释的更加淋漓尽致,因为可执行文件在启动运行之前都不知道需要依赖哪些动态库,只在运行时根据代码的需要再进行动态链接。
动态链接下可执行文件的生成
在动态链接下,链接器并不是将动态库中的代码和数据拷贝到可执行文件中,而是将动态库的必要信息写入了可执行文件,这样当可执行文件在加载时就可以根据此信息进行动态链接了。(这里我们以Linux下可执行文件即ELF文件为例)。
在动态链接下,可执行文件当中会新增两段,即dynamic段以及GOT(Global offset table)段,这两段内容就是是我们之前所说的必要信息。dynamic段中保存了可执行文件依赖哪些动态库,动态链接符号表的位置以及重定位表的位置等信息。可执行文件在加载时就可以根据此信息进行动态链接了。