Cmake基础实例:如何编译【跨平台】动态库和应用程序】。很久以前在B站上传了几个小视频,介绍了在Windows和Linux程序这两个平台下如何通过cmake和make这两个构建工具编译链接动态库、静态库和可执行文件。视频中的示例代码是提前写好的,所以重点在构建(Build)环节。主要介绍了动态库与动态库之间、应用程序与动态库之间的引用等。熟悉动态库和静态库的朋友应该能轻松看懂里面的内容。但是不熟悉C语言的朋友们似乎还是有点障碍。本文主要对视频中的示例代码进行了精简,只使用了一个动态库和一个可执行文件,并使用cmake构建工具演示了Windows和Linux两种平台下的构建过程。这篇文章的内容很基础,可以算是使用cmake构建跨平台程序的入门教程!示例代码先看一下测试代码的全貌:mylib:只有一个源文件,编译输出一个动态库;myapp:只有一个源文件,链接mylib动态库,编译输出一个可执行程序;mylib在mylib目录下,一共有3个文件:mylib.h、mylib.c和CMakeLists.txt,内容如下:#ifdefMYLIB_EXPORT#defineMYLIB_API__declspec(dllexport)#else#defineMYLIB_API__declspec(dllimport)#endif#endifMYLIB_APIintmy_add(intnum1,intnum2);MYLIB_APIintmy_sub(intnum1,intnum2);#endif//_MY_LIB_以上这个代码,主要是在一个动态库中使用的exportWindows系统,Linux系统不需要。补充:在windows系统下,编译动态库时会生成xxx.dll和xxx.lib。xxx.dll是真正的库文件指令,xxx.lib只是一个符号表。具体:在Windows系统中,编译动态库时,打开(定义)宏MYLIB_EXPORT,下面的宏生效:#defineMYLIB_API__declspec(dllexport)此时,my_add和my_sub这两个函数的符号可能会导出到.lib文件。当应用程序(myapp)使用这个动态库时,当myapp.c包含mylib.h时,关闭宏MYLIB_EXPORT,此时下面的宏会生效:#defineMYLIB_API__declspec(dllimport)为了简化复杂度宏定义的,这里不考虑静态库。读完头文件,我们再看一下mylib.c的源文件://mylib/mylib.cfile#include"mylib.h"intmy_add(intnum1,intnum2){return(num1+num2);}intmy_sub(intnum1,intnum2){return(num1-num2);}最后看一下mylib/CMakeLists.txt文件://mylib/CMakeLists.txt文件CMAKE_MINIMUM_REQUIRED(VERSION3.5)PROJECT(mylibVERSION1.0.0)#Custommacros,ADD_DEFINITIONS代码中可以使用(-DMYLIB_EXPORT)#头文件INCLUDE_DIRECTORIES(./)#源文件FILE(GLOBMYLIB_SRCS"*.c")#编译目标ADD_LIBRARY(${PROJECT_NAME}SHARED${MYLIB_SRCS})不多说了cmake的语法,这里只用到一小部分。注意一点:ADD_DEFINITIONS(-DMYLIB_EXPORT),因为这个CMakeLists.txt是用来编译动态库的,所以在Windows平台下,需要在每个导出符号前加上__declspec(dllexport),所以需要打开宏定义:MYLIB_EXPORT。myapp应用的代码就更简单了,只有两个文件:myapp.c和CMakeLists.txt,内容如下://myapp/myapp.cfile#include#include#include"mylib.h"intmain(intargc,char*argv[]){intret1,ret2;inta=5;intb=2;ret1=my_add(a,b);ret2=my_sub(a,b);printf("ret1=%d\n",ret1);printf("ret2=%d\n",ret2);getchar();return0;}HelloWorld级别的代码,无需解释!CMakeLists.txt内容如下://myapp/CMakeLists.txt文件CMAKE_MINIMUM_REQUIRED(VERSION3.5)PROJECT(myappVERSION1.0.0)#头文件路径INCLUDE_DIRECTORIES(./include)#库文件路径LINK_DIRECTORIES(./lib)#源文件FILE(GLOBMYAPP_SRCS"*.c")#编译目标ADD_EXECUTABLE(${PROJECT_NAME}${MYAPP_SRCS})#依赖动态库TARGET_LINK_LIBRARIES(${PROJECT_NAME}mylib)最后一行TARGET_LINK_LIBRARIES(${PROJECT_NAME}mylib)表示应该链接动态库mylib。那么到哪个目录去寻找对应的头文件和库文件呢?用这两行指定查找目录:#头文件路径INCLUDE_DIRECTORIES(./include)#库文件路径LINK_DIRECTORIES(./lib)这两个目录暂时关闭不存在,我们稍后编译时手动创建。mylib的输出文件可以在编译时自动复制到指定目录下。但是为了不使问题复杂化,一些操作步骤是手动完成的,这样可以更清楚地理解链接过程。最后留下最外层的CMakeLists.txt文件:CMAKE_MINIMUM_REQUIRED(VERSION3.5)PROJECT(cmake_demoVERSION1.0.0)SET(CMAKE_C_STANDARD99)#Custommacros,if(CMAKE_HOST_UNIX)ADD_DEFINITIONS(-DMY_LINUX)else()ADD_DEFINITIONS(-DMY_WINDOWS)endif()ADD_SUBDIRECTORY(mylib)ADD_SUBDIRECTORY(myapp)它所做的主要工作是:根据不同的平台,定义相应的宏,添加mylib和myapp这两个子文件夹。Linux下build过程的cmake配置为了不污染源文件目录,在最外层目录下新建一个build目录,然后执行cmake命令:$cd~/tmp/cmake_demo/$mkdirbuild$cdbuild/$ls$cmake..此时,在build目录下,生成如下文件:CMakeCache.txtCMakeFilescmake_install.cmakeMakefilemyappmylibmake编译我们可以分别进入mylib和myapp目录,执行make命令分别编译,或者直接编译所有target在构建目录中。现在直接在构建目录中编译所有目标:$cd~/tmp/cmake_demo/build$makeScanningdependenciessofttargetmylib[25%]BuildingCobjectmylib/CMakeFiles/mylib.dir/mylib.c.o[50%]LinkingCsharedlibrarylibmylib.so[50%]BuilttargetmylibScanningdependenciessofttargetmyapp[75%]BuildingCobjectmyapp/CMakeFiles/myapp.dir/myapp.c.o~/tmp/cmake_demo/myapp/myapp.c:4:19:fatalerr:mylib.h:没有这样的文件或目录#include"mylib.h"^compilationterminated.myapp/CMakeFiles/myapp.dir/build.make:62:目标'myapp/CMakeFiles/myapp.dir/myapp.c.o'的配方失败make[2]:***[myapp/CMakeFiles/myapp.dir/myapp.c.o]Error1CMakeFiles/Makefile2:140:recipefortarget'myapp/CMakeFiles/myapp.dir/all'failedmake[1]:***[myapp/CMakeFiles/myapp.dir/all]Error2Makefile:83:recipefortarget'all'failedmake:***[all]Error2从提示信息可以看出:./mylib/libmylib.so文件已经编译生成,但是在编译可执行程序myapp时遇到错误:找不到mylib.h文件!在刚才myapp的介绍/CMakeLists.txt文件中说:应用程序寻找头文件的目录是myapp/include,寻找库文件的目录是myapp/lib。但是这两个目录以及对应的头文件和库文件是不存在的!因此,我们需要手动创建它们,将头文件mylib.h和库文件libmylib.so复制到其中。运行过程如下:$cd~/tmp/cmake_demo/myapp/$mkdirincludelib$cp~/tmp/cmake_demo/mylib/mylib.h./include/$cp~/tmp/cmake_demo/build/mylib/libmylib。so./lib/注:刚才编译的库文件libmylib。在构建目录中也是如此。准备好头文件和库文件后,再次编译:$cd~/tmp/cmake_demo/build/$make[50%]Builttargetmylib[75%]BuildingCobjectmyapp/CMakeFiles/myapp.dir/myapp.c.o[100%]LinkingCexecutablemyapp[100%]Builtargetmyapp此时在build/myapp目录下生成可执行文件myapp。测试执行$cd~/tmp/cmake_demo/build/myapp$./myappret1=7ret2=3完美!由于我们是在build目录下编译,编译过程中的所有输出和中间文件都放在build目录下,完全不会污染源文件。Windows下的构建过程删除了Linux系统中的build文件夹,然后将测试代码进行压缩,复制到Windows系统中继续测试。在Windows下编译时,很少使用命令行,大部分都是使用VS或VSCode编译。打开VSCode,然后打开测试代码文件夹cmake_demo:因为需要使用cmake工具构建,所以需要在VSCode中安装cmake插件。(如何安装VSCode插件我就不赘述了)第一步:cmake配置按键盘ctrl+shift+p,在命令窗口选择Cmake:Configure。如果您没有看到此选项,请手动输入前几个字符。然后就可以智能匹配了:第一次配置的时候,会弹出如下选项选择编译器:这里我们选择64位的amd64。配置的结果在下方窗口的output标签中输出,如下图:这说明cmake配置成功,各个文件夹下的CMakeLists.txt文件被正确执行。这个时候我们再看看资源管理器里的变化:自动生成了build目录,里面的文件如下:好像和linux系统里的流程是一样的,只不过这里VSCode拿走了主动帮我们做点什么。Step2:编译配置完成后,接下来就是编译了。按shift+F7,或者点击VSCode底部的Build图标:弹出编译目标列表:这里选择ALL_BUILD,即编译所有目标:mylib和myapp,输出如下:看一下编译后的输出文件:mylib.dll是编译后的动态链接库,mylib.lib是一个导入符号。myapp.exe是编译后的可执行程序。第三步:执行我们先在命令行窗口执行myapp.exe:提示错误:找不到动态链接库!手动复制mylib.dll到myuapp.exe所在目录,然后再次执行myapp.exe:完美!不过既然已经使用了VSCode编译,那我们就继续在VSCode中调试代码吧。按调试快捷键F5,第一次会弹出调试器选项:选择LLDB,然后会弹出错误对话框:因为我们没有提供相应的配置文件告诉VSCode要调试哪个可执行程序。点击【确定】后,VSCode会自动为我们生成.vscode/launcher.json文件,内容如下:将其中的program项修改为可执行程序的全路径:"program":"F:/tmp/cmake_demo/build/myapp/Debug/myapp.exe”然后再次按F5键,这次终于可以正确执行了:此时可以在mylib.c或myapp.c中设置断点,并然后执行单步调试程序A:本文转载自微信公众号《IOT物联网小镇》