Windows和Linux下的动态链接库的编译、链接和引用相关问题

关于动态链接库和静态链接库的区别可以参考另一篇笔记,其实也就一句话:静态链接库在编译期加载到相应的编译目标中(编译后不再需要),而动态链接库是在运行期由执行目标进行调用(每次运行都需要)。
Windows下动态链接库以后缀.dll标识,Linux下的动态链接库命名为libxxx.so,其中xxx为库名称。

Windows下配置VS编译生成dll动态库和相应的lib文件

当我们的项目生成为DLL库提供给调用方使用时,同时还需要提供lib文件和头文件,如果只有头文件,VS编译时将会报错,因为编译器需要从相应的lib中查找需要引用的相关函数。该lib文件与静态库的lib文件不同的是它要小的多,其中并不包含实际相关函数实现,只是包含了需要引用的函数声明信息。该lib同样也只是在调用方编译时用一次,运行时只需要提供相应的DLL即可。所以我们除了创建能够生成DLL的项目之外,还需要配置VS生成包含指定函数的lib文件。

创建生成DLL的项目

创建VS的WIN32应用程序项目时,将“应用程序类型”直接选择为DLL即可。也可以创建一个普通的控制台项目,然后在项目属性——常规——配置类型中设置为动态库(.dll)。

配置输出lib和dll

创建完DLL项目,或者在项目属性中更改项目类型为DLL之后,编译生成的目标文件即为相应的DLL,我们只需要配置能够生成lib即可。可以指定某些函数生成到lib中,相当于我们只暴露指定的函数给调用方。有2种方式来指定生成lib文件:

通过_declspec(dllexport)生成lib

只需要在要导出的函数声明前使用_declspec(dllexport)即可导出为lib,该方式不是很灵活,当需要添加函数时,添加的函数都需要在源码中添加该声明。
下面举例说明:
创建一个DLL工程
新建头文件testDll.h,内容如下:

1
2
3
4
5
6
7
8
#ifndef TEST_DLL_H
#define TEST_DLL_H

#include <string>

_declspec(dllexport) void print(const std::string &str);

#endif

新建testDll.cpp,内容如下:

1
2
3
4
5
6
7
#include "testDll.h"
#include <iostream>

void print(const std::string &str)
{
std::cout << "Come from dynamic library!\n" << str << std::endl;
}

然后编译之后可以看到生成了TestDll.libTestDll.dll两个文件,如果上面在声明时不使用_declspec(dllexport),只会生成dll,不会生成lib

引用DLL

同一个解决方案下新建一个项目TestDemo,新建一个解决方案也是一样的,在同一个解决方案中省了指定lib路径和拷贝dll。
新建testDemo.cpp,内容为:

1
2
3
4
5
6
7
#include "testDll.h"

int main(void)
{
print("Hello World!");
return 0;
}

编辑项目属性,其中“通用属性——引用”中添加刚才那个DLL的项目,最好再启用“复制本地”,这样DLL生成之后就会自动引用到这里。在“配置属性——C/C++——常规”中附加包含目录添加含有testDll.h的路径。

现在编译之后就可以直接运行了。

附送一份跨平台的通用宏定义:

1
2
3
4
5
6
7
8
9
10
11
12
#ifdef WIN32
#ifdef _WINDLL
#define DLL_API _declspec(dllexport) // 用于DLL生成的导入导出
#else
#define DLL_API _declspec(dllimport)
#endif // _WINDLL
#else
#define DLL_API
#ifndef __stdcall // 用于指定函数参数调用中的压栈和清理顺序
#define __stdcall
#endif // __stdcall
#endif // WIN32

该部分内容放在用于生成DLL项目的相关头文件中,后面需要导出到DLL中的函数都用宏DLL_API修饰,这样当这个项目编译时将会自动生成相应的DLL文件和lib文件,lib文件中将会含有被_declspec(dllexport)修饰的函数,以便用于其他项目的链接。

通过.def文件生成lib

也可以在项目中添加一个后缀为.def的文件,文件内容如下来指定DLL导出的文件相关信息,例如针对上面的代码,删除其中的_declspec(dllexport),添加文件Dllexport.def(新建项,然后搜索def即可),内容:

1
2
3
4
5
; export for dll
LIBRARY TestDll

EXPORTS
print

其中;后面为注释,LIBRARY后面跟导出库的名字,EXPORTS后面为需要导出库中包含的函数。
这种方式更加灵活

Linux下动态链接库的生成和调用

linux下容易的多,只需要生成一个libxxx.so文件,然后引用的时候在编译期通过-lxxx加入对该so的引用即可。
还以上面例子中的三个代码文件为例,

编译动态链接库

1
g++ -fPIC -shared -o libtestDll.so

其中-shared指定生成动态链接库,否则默认输出可执行文件,-fPIC表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。

引用动态链接库

1
g++ -o testDemo testDemo.cpp -ltestDll -L.

其中-l表示引用的动态链接库名,-L.用于指定动态链接库查找路径包含当前目录。
编译完成之后运行会报错,动态链接库不存在,需要指定动态链接库运行时查找路径:

1
export LD_LIBRARY_PATH=.

或者将需要的路径加入到/etc/ld.so.conf后,运行sudo ldconfig来更新缓存。

关于Linux下动态链接库编译、链接和运行时加载的问题可以参考这里:http://notes.maxwi.com/2017/04/13/linux-dynamic-lib-ldconfig/