C++动态库使用
前言
Windows与Linux下面的动态链接库区别
1. 文件后缀不同
Linux动态库的后缀是 .so 文件,而window则是 .dll 文件。
2. 文件格式不同
(a)Linux下是ELF格式,即Executable and Linkable Format
在ELF之下,共享库中所有的全局函数和变量在默认情况下都可以被其它模块使用,即ELF默认导出所有的全局符号。
(b)Windows下面是PE格式的文件,即Portable Executable Format
DLL文件和EXE文件实际上是一个概念,都是PE格式的二进制文件。DLL需要显示地“告诉”编译器需要导出某个符号,否则编译器默认所有的符号都不导出。
3. 动态链接库的文件个数不一样
Linux的动态链接库就只有一个 .so 文件,还有与之对应的头文件,而在Windows下面的动态库有两个文件,
一个是引入库(.LIB)文件,
一个是动态库(.DLL)文件,
需要的头文件(.h)文件
(1)LIB引入库文件包含被DLL导出的函数名称和位置,对于导入库而言,其实际的执行代码位于动态库中,导入库只包含了地址符号表等,确保程序找到对应函数的一些基本地址信息。
(2)DLL文件包含实际的函数和数据,应用程序使用LIB文件链接到所需要使用的DLL文件,库中的函数和数据并不复制到可执行文件中,因此在应用程序的可执行文件中,存放的不是被调用的函数代码,而是DLL中所要调用的函数的内存地址,这样当一个或多个应用程序运行是再把程序代码和被调用的函数代码链接起来,从而节省了内存资源。
Windows上动态库使用
1. 创建动态库
windows上创建动态库一般有两种方式:
使用
_declspec显示声明要导出的对象 。和平时写程序一样,源码中不需要显示声明导出,导出放在
def文件中声明。
首先介绍下使用
_declspec来创建动态库的过程:
(1)新建一个空项目或者是使用DLL模板都可以。(个人习惯用干净的空项目)
(2)修改项目属性输出类型改为dll。

(3)正常添加.h与.cpp文件,.h中要导出的函数前添加_declspec(dllexport)声明即可。


(4)重新生成,在工程目录即可生成对应的dllapi.lib与dllapi.dll文件。

再来看看使用def文件声明导出的方式:
(1)添加def文件

(2)def文件中声明要导出的函数
1 | LIBRARY |
(3)重新生成,和第一种显示声明方式一样生成了.lib和.dll2个文件。


2. 使用动态库
windows上使用动态库一般有2种方式:
- 隐式调用(IDE上设置)
- 显示调用
下面分别介绍下详细的使用流程
隐式调用使用流程
(1)创建控制台测试工程,并建立一个依赖目录,将动态库的.h和.lib放在这个目录下,同时将.dll放在exe可执行程序同级目录。

(2)配置
项目->属性->配置属性->VC++ 目录-> 在“包含目录”里添加头文件
DllAPI.h所在的目录 。项目->属性->配置属性->VC++ 目录-> 在“库目录”里添加依赖文件
dllapi.lib所在的目录。项目->属性->配置属性->链接器->输入-> 在“附加依赖项”里添加
dllapi.lib。 你也可以在代码中添加一行设置库的链接,#pragma comment(lib, "dllapi.lib"),这样就不需要2、3配置了。

(3)测试运行

显示调用使用流程
只需要将.dll放在exe可执行程序同级目录就行了,IDE不需要额外设置。注意要使用这个dll中的方法其创建时必须要_declspec 显示导出,使用时只要这个dll文件就行了,.h和.lib不需要。
1 |
|
Linux上动态库使用
Linux上创建动态库很简单,不需要显示声明导出的函数,它会默认导出。
在Linux上使用动态库也有2种方式:
- 编译器链接
- 库文件加载
1. 编译器链接使用流程
- 编写源文件。
- 将一个或几个源文件编译链接,生成libxxx.so。
- 通过
-L<path> -lxxx的gcc选项链接生成的libxxx.so。- 把libxxx.so放入链接库的标准路径,或指定
LD_LIBRARY_PATH,才能运行链接了libxxx.so的程序。
(1) 编写源文件,生成so共享库
建立一个源文件:add.c,代码如下:
1 | int add(int x, int y) |
编译生成libadd.so:
1 | gcc -fPIC -shared -o libadd.so add.c |
我们会得到libadd.so。
实际上上述过程分为编译和链接两步, -fPIC是编译选项,PIC是 Position Independent Code 的缩写,表示要生成位置无关的代码,这是动态库需要的特性; -shared是链接选项,告诉gcc生成动态库而不是可执行文件。
上述的一行命令等同于:
1 | gcc -c -fPIC add.c |
(2)为动态库编写接口文件
1 |
|
(3)测试,链接动态库生成可执行文件
建立一个使用add函数的test.c,代码如下:
1 |
|
gcc test.c -L. -ladd 生成a.out,其中-ladd表示要链接libadd.so。-L.表示搜索要链接的库文件时包含当前路径。
注意,如果同一目录下同时存在同名的动态库和静态库,比如 libadd.so 和 libadd.a 都在当前路径下,
则gcc会优先链接动态库。
(4) 运行
运行 ./a.out 会得到以下的错误提示。
1 | ./a.out: error while loading shared libraries: libadd.so: cannot open shared object file: No such file or directory |
找不到libadd.so,原来Linux是通过 /etc/ld.so.cache 文件搜寻要链接的动态库的。
而 /etc/ld.so.cache 是 ldconfig 程序读取 /etc/ld.so.conf 文件生成的。
(注意, /etc/ld.so.conf 中并不必包含 /lib 和 /usr/lib,ldconfig程序会自动搜索这两个目录)
如果我们把 libadd.so 所在的路径添加到 /etc/ld.so.conf 中,再以root权限运行 ldconfig 程序,更新 /etc/ld.so.cache ,a.out运行时,就可以找到 libadd.so。
因此我们可以为a.out指定 LD_LIBRARY_PATH运行,如下:
1 | LD_LIBRARY_PATH=. ./a.out |
程序就能正常运行了。LD_LIBRARY_PATH=. 是告诉 a.out,先在当前路径寻找链接的动态库。
或者修改LD_LIBRARY_PATH环境变量,指定为当前目录也可以,如下:
1 | export LD_LIBRARY_PATH=${pwd}:${LD_LIBRARY_PATH} |
然后直接执行./a.out也可以运行了。
2. 库文件加载使用流程
像window调用库文件一样,在linux下,也有相应的API因为加载库文件而存在。它们主要是以下几个函数:
使用源码如下:
1 | // test2.c |
编译,运行./test2可以看到结果为3。
1 | gcc test2.c -o test2 -ldl |