(一)简介
对于大型软件开发,动态连接库是必不可少的。不仅可以实现模块的封装,而且可以实现软件的热更新(即替换windows下的.dll或Linux下的.so文件后直接实现软件更新,无需重新编译)。有时也需要使用静态链接库,在一定程度上相比于动态链接库运行速度更快。
本文介绍使用CMake生成以及调用动态连接库和静态链接库的方法,只关注易出错的地方,完整项目demo代码见https://download.csdn.net/download/weixin_43325228/89913651。
(二)Windows系统
在windows系统中,我们使用visual studio 2022编译器编译.dll文件和.lib文件
(1)动态链接库 dll
visual studio 2022编译器最大的特点是不会自动导出代码中的符号(需要额外设置),所以务必指明需要导出那些符号。既可以使用.def文件指明,也可以用__declspec(dllexport)
指明,通常建议采用第二种方式:
extern "C" __declspec(dllexport) void initMyClass(double num);
extern "C" __declspec(dllexport) void addToMyInt(int num);
extern "C" __declspec(dllexport) void printMyInt();
简单言之,需要在函数声明(或变量声明)的前方写上__declspec(dllexport)
,以表示需要导出符号。特别地,前缀extern "C"
表示导出标准的C语言符号,也就是说导出“干净的”C语言符号而非添加了修饰的“不干净”的C++语言符号。这样有助于规范接口,提升兼容性,实现大型软件的松耦合和热更新。
※ 如何判断是否导出了“干净的”C语言符号呢?
可以用记事本打开编译后的dll文件,如果发现函数名称后面有一些后缀,则说明没有使用extern "C"
如果函数名称后面没有后缀,则说明使用了extern "C"
CMake构建项目时需要指明生成动态链接库SHARED(否则默认是静态的),CMakeLists.txt
文件如下
project(buildDynamicLink)
set(LIBRARY_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/bin)
add_library(buildDynamicLink SHARED API.cpp myClass.cpp)
这样就可以完成dll的编译了
通常会得到三个文件,.dll是运行时需要,包含了算法的实现,.lib是编译时需要,包含了符号,.exp没用。需要注意的是这里的.lib与后文讲的静态链接库不同,只是供编译器查找符号并链接用的,不包含算法的具体实现内容。
※ 如何使用dll呢?
如果要使用dll,首先需要拿到接口头文件
extern "C" __declspec(dllimport) void initMyClass(double num);
extern "C" __declspec(dllimport) void addToMyInt(int num);
extern "C" __declspec(dllimport) void printMyInt();
其次编写CMakeLists.txt
文件
project(useDynamicLink)
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/bin)
add_executable(useDynamicLink main.cpp)
target_link_libraries(useDynamicLink buildDynamicLink)
这里的target_link_libraries
用于指明需要链接那些库,buildDynamicLink
是指buildDynamicLink.lib
文件而非buildDynamicLink.dll
文件
编译完成之后所得的exe文件需要配合dll文件才能正常使用,否则会报错:找不到dll。通常将dll放在与exe同一个目录下
(2)静态库 lib
与动态链接库不同,visual studio 2022编译器编译静态库时会自动导出所有符合。因此不需要人工声明哪些符号需要导出,但是依然建议使用extern "C"
增加兼容性
extern "C" void initMyClass(double num);
extern "C" void addToMyInt(int num);
extern "C" void printMyInt();
CMakeLists.txt文件如下
project(buildStaticLib)
set(LIBRARY_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/bin)
add_library(buildStaticLib STATIC API.cpp myClass.cpp)
编译后会得到一个lib文件
※ 如何使用lib呢?
如果要使用lib,首先需要拿到接口头文件
extern "C" void initMyClass(double num);
extern "C" void addToMyInt(int num);
extern "C" void printMyInt();
其次编写CMakeLists.txt
文件
project(useStaticLib)
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/bin)
add_executable(useStaticLib main.cpp)
target_link_libraries(useStaticLib buildStaticLib)
最终编译得到的exe通常会比lib文件大,这是因为静态库的原理就是将lib嵌入到exe中。这也就意味着如果lib文件变化了,需要重新编译exe才能得到更新。相比较而言,如使用动态链接库方案,如果dll文件变化了而接口没变,那么直接用新的dll文件替换旧的dll文件即可完成热更新,无需重新编译exe。
(三) Linux系统
在linux系统中,我们使用gcc编译器编译.so文件和.a文件
(1)动态链接库 so
gcc编译器的最大特点是自动将所有符号导出,所以不用(也不能)写__declspec(dllexport)
语句。当然,还是建议接口符号添加extern "C"
前缀以提升兼容性。
extern "C" void initMyClass(double num);
extern "C" void addToMyInt(int num);
extern "C" void printMyInt();
其余部分与windows类似,编译后只会得到一个文件libbuildDynamicLink.so
,so文件的使用方法与dll类似
(2)静态库 a
Linux的静态库编译和使用方法与Windows类似,会形成一个文件libbuildStaticLib.a
其余部分与windows类似,不再赘述