本文的内容很基础,算是使用cmake来建立跨平台程序的入门教程吧!示例代码
首先看一下测试代码的概貌:
mylib:只有一个源文件,编译输出一个动态库;myapp:也只有一个源文件,链接mylib动态库,编译输出一个可执行程序;
mylib
在mylib目录中,一共有3个文件:mylib.h,mylib.c以及CMakeLists.txt,内容分别如下:
// mylib/mylib.h w文件
#ifndef _MY_LIB_
#define _MY_LIB_
#ifdef MY_LINUX
#define MYLIB_API extern
#else
#ifdef MYLIB_EXPORT
#define MYLIB_API __declspec(dllexport)
#else
#define MYLIB_API __declspec(dllimport)
#endif
#endif
MYLIB_API int my_add(int num1, int num2);
MYLIB_API int my_sub(int num1, int num2);
#endif // _MY_LIB_
以上这个代码,主要是用在Windows系统的动态导入库,在Linux系统中,不是必要的。
补充:在windows系统中,编译动态库时会生成xxx.dll和xxx.lib。xxx.dll中是真正的库文件指令,xxx.lib中仅仅是符号表。
具体来说:在Windows系统中,当编译动态库的时侯,打开(定义)宏MYLIB_EXPORT,下边这个宏生效:
#define MYLIB_API __declspec(dllexport)
这样的话,两个函数my_add和my_sub的符号才可能被导入到mylib.lib文件中。
当这个动态库被应用程序(myapp)使用的时侯,myapp.c在includemylib.h的时,关掉宏MYLIB_EXPORT,此时下边这个宏就生效:
#define MYLIB_API __declspec(dllimport)
为了简化宏定义的复杂度,这儿就不考虑静态库了。
看完了头文件,再来看看源文件mylib.c:
// mylib/mylib.c 文件
#include "mylib.h"
int my_add(int num1, int num2)
{
return (num1 + num2);
}
int my_sub(int num1, int num2)
{
return (num1 - num2);
}
最后再来看一下mylib/CMakeLists.txt文件:
// mylib/CMakeLists.txt 文件
CMAKE_MINIMUM_REQUIRED(VERSION 3.5)
PROJECT(mylib VERSION 1.0.0)
# 自定义宏,代码中可以使用
ADD_DEFINITIONS(-DMYLIB_EXPORT)
# 头文件
INCLUDE_DIRECTORIES(./)
# 源文件
FILE(GLOB MYLIB_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.c 文件
#include
#include
#include "mylib.h"
int main(int argc, char *argv[])
{
int ret1, ret2;
int a = 5;
int b = 2;
ret1 = my_add(a, b);
ret2 = my_sub(a, b);
printf("ret1 = %d n", ret1);
printf("ret2 = %d n", ret2);
getchar();
return 0;
}
HelloWorld级别的代码,不须要多解释!CMakeLists.txt内容如下:
// myapp/CMakeLists.txt 文件
CMAKE_MINIMUM_REQUIRED(VERSION 3.5)
PROJECT(myapp VERSION 1.0.0)
# 头文件路径
INCLUDE_DIRECTORIES(./include)
# 库文件路径
LINK_DIRECTORIES(./lib)
# 源文件
FILE(GLOB MYAPP_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(VERSION 3.5)
PROJECT(cmake_demo VERSION 1.0.0)
SET(CMAKE_C_STANDARD 99)
# 自定义宏,代码中可以使用
if (CMAKE_HOST_UNIX)
ADD_DEFINITIONS(-DMY_LINUX)
else ()
ADD_DEFINITIONS(-DMY_WINDOWS)
endif()
ADD_SUBDIRECTORY(mylib)
ADD_SUBDIRECTORY(myapp)
它所做的主要工作就是:按照不同的平台,定义相应的宏,但是添加了mylib和myapp这两个子文件夹。
【点击链接免费学习c/c++linux服务器开发相关技术】
须要C/C++Linux服务器构架师学习资料加qun812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDBlinux操作系统界面,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,解释器linux查看磁盘空间,DPDK,ffmpeg等),免费分享
Linux下建立过程cmake配置
为了不污染源文件目录,在最内层目录下新建build目录,之后执行cmake指令:
$ cd ~/tmp/cmake_demo/
$ mkdir build
$ cd build/
$ ls
$ cmake ..
此时,在build目录下,形成如下文件:
CMakeCache.txt CMakeFiles cmake_install.cmake Makefile myapp mylib
make编译
我们可以分别步入mylib和myapp目录,执行make指令来单独编译,也可以直接在build目录下编译所有的目标。
如今就直接在build目录下编译所有目标:
$ cd ~/tmp/cmake_demo/build
$ make
Scanning dependencies of target mylib
[ 25%] Building C object mylib/CMakeFiles/mylib.dir/mylib.c.o
[ 50%] Linking C shared library libmylib.so
[ 50%] Built target mylib
Scanning dependencies of target myapp
[ 75%] Building C object myapp/CMakeFiles/myapp.dir/myapp.c.o
~/tmp/cmake_demo/myapp/myapp.c:4:19: fatal error: mylib.h: 没有那个文件或目录
#include "mylib.h"
^
compilation terminated.
myapp/CMakeFiles/myapp.dir/build.make:62: recipe for target 'myapp/CMakeFiles/myapp.dir/myapp.c.o' failed
make[2]: *** [myapp/CMakeFiles/myapp.dir/myapp.c.o] Error 1
CMakeFiles/Makefile2:140: recipe for target 'myapp/CMakeFiles/myapp.dir/all' failed
make[1]: *** [myapp/CMakeFiles/myapp.dir/all] Error 2
Makefile:83: recipe for target 'all' failed
make: *** [all] Error 2
从提示信息中看出:早已编译生成了./mylib/libmylib.so文件,并且在编译可执行程序myapp时遇见了错误:找不到mylib.h文件!
在刚刚介绍myapp/CMakeLists.txt文件时说到:应用程序查找头文件的目录是myapp/include,查找库文件的目录是myapp/lib。
然而这2个目录以及相应的头文件、库文件都不存在!
为此我们须要自动创建,而且把头文件mylib.h和库文件libmylib.so拷贝进去,操作过程如下:
$ cd ~/tmp/cmake_demo/myapp/
$ mkdir include lib
$ cp ~/tmp/cmake_demo/mylib/mylib.h ./include/
$ cp ~/tmp/cmake_demo/build/mylib/libmylib.so ./lib/
注意:刚刚编译生成的库文件libmylib.so是在build目录下。
打算好头文件和库文件以后,再度编译一下:
$ cd ~/tmp/cmake_demo/build/
$ make
[ 50%] Built target mylib
[ 75%] Building C object myapp/CMakeFiles/myapp.dir/myapp.c.o
[100%] Linking C executable myapp
[100%] Built target myapp
此时,就在build/myapp目录下生成可执行文件myapp了。
测试、执行
$ cd ~/tmp/cmake_demo/build/myapp
$ ./myapp
ret1 = 7
ret2 = 3
完美!
因为我们是在build目录下编译的,编译过程中所有的输出和中间文件,都置于build目录下,一点都没有污染源文件。
Windows下建立过程
把Linux系统中的build文件夹删掉,之后把测试代码压缩,复制到Windows系统中继续测试。
在Windows下编译,通常就极少使用命令行了,大部份都使用VS或则VSCode来编译。
打开VSCode,之后打开测试代码文件夹cmake_demo:
由于须要使用cmake工具来建立,所以须要在VSCode安装cmake插件。(怎么安装VSCode插件就不赘言了)
第一步:cmake配置
按下按键ctrl+shift+p,在命令窗口中选择Cmake:Configure,倘若没听到这个选项,就自动输入后面的几个字符,之后就可以智能匹配到:
在第一次Configure的时侯,会弹出下边的选项,来选择编译器:
我们这儿选择64位的amd64。
配置的结果输出在最下边窗口中的output标签中,如下所示:
这就表明cmake配置成功,正确的执行了每一个文件夹下的CMakeLists.txt文件。
这个时侯,来看一下资源管理器中有啥变化:手动生成了build目录,其中的文件如下:
看来,流程与Linux系统中都是一样的,只不过这儿是VSCode主动帮我们做了一些事情。
第二步:编译
配置以后,下一步就是编译了。
按下shift+F7linux 编译生成so文件,或则单击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调试哪一个可执行程序。
单击[OK]过后,VSCode会手动为我们生成.vscode/launcher.json文件linux 编译生成so文件,内容如下:
把其中的program项目,改成可执行程序的全路径:
"program": "F:/tmp/cmake_demo/build/myapp/Debug/myapp.exe"
之后再度按下F5键,这回总算可以正确执行了:
此时,就可以在mylib.c或则myapp.c中设置断点,之后进行单步调试程序了: