C/C++跨平台构建工具CMake-----灵活添加库并实现开发和生产环境的分离

目录

  • 1.概述
  • 2.创建项目
  • 3 配置运行项目
    • 3.1 编写开平方根示例代码
    • 3.2 编写CMake构建脚本
  • 4.使用子模块实现求平方根的功能
    • 4.1 在子模块中实现两种求平方根的方法
    • 4.2 构建Mathfunctions子模块
    • 4.3 在根目录引用子模块的功能
      • 4.3.1 编写构建脚本
      • 4.3.2 编写C++代码使用MathFunctions库中提供的功能
      • 4.4 运行
  • 5 添加可调试的功能
    • 5.1 编写子模块的CMakeLists.txt
    • 5.2 在C++代码中使用USE_MYMATH变量
    • 5.3 运行代码

1.概述

平时我们开发C/C++程序时都是在类似Window 的Visual Studio这类的IDE上,这些IDE集成了各种好用的工具,并提供了强大的代码调试和搜索等功能,能够很高效的帮助我们实现我们想要的功能。既然这样,那为啥我们还需要需要学习CMake的构建工具呢?我学习的主要原因是:我是一个Android开发者,作为Android开发者,熟悉jni的开发是重中之重,界面开发的时代已经过去,现在Android在VR领域和音视频邻域的地位逐渐的上升,而这些领域无不需要jni的加持,而现在的Andoid jni都是使用Cmake辅助构建的,所以学习Cmkae构建工具很有必要。

在本文中,我们将会学习如何使用Cmake工具创建一个项目,然后介绍如何在项目中添加和使用库以及如何将我们的库设置成可以动态切换的。这里可动态切换的意思是,当我们在开发环境时使用开发环境的库,在生产环境时使用生产环境的库。

注意:本文内容是Cmake官方练习的第二个练习,读者也可以直接去官方下载练习自己动手体验

2.创建项目

当我们在集成开发环境(IDE)中创建项目特别简单,只需要点击file->new project就能创建一个项目,IDE会帮我们把基本的结构准备好。但是我们今天直接自己手动创建。如下图所示:
在这里插入图片描述在上面的截图中,我们创建了一个根目录AddLibraryDemo表示整个项目的主目录,在这个目录下,我们创建了build目录,用于存放编译后的产物,创建MathFunctions表示子模块目录,因为我们的项目可能会由很多的模块组成。创建CMakeLists.txt作为整个项目的构建脚本。而tutorial.cxx是本文中的演示程序,TutorialConfig.h.in是为了配置动态编译生成头文件的配置文件。

接下来我们看下在子模块MathFunctions中的目录结构及配置:
在这里插入图片描述在MathFunctions的子模块中,目录结构特别简单,就是一个构建脚本CMakeLists.txt和需要演示的C++文件。接下来我们就介绍下如何使用CMake配置并运行我们创建的AddLibraryDemo项目。

3 配置运行项目

首先,我们先使用C++编写一个简单的功能,数学中的开平方根,为了演示,在本项目中会使用两种方式开平方根,一种是使用系统标准库中的函数std::sqrt(x),另外一种是使用自己实现的开平方根的方式。这个例子来源于官网的练习题。

3.1 编写开平方根示例代码

接下来我们看下实现开平方根的例子。在我们创建的根目录下的tutorial.cxx中编写开平方根的代码,代码如下所示:

#include <cmath>
#include <iostream>
#include <string>
#include "TutorialConfig.h"int main(int argc, char* argv[])
{if (argc < 2) {// report versionstd::cout << argv[0] << " Version " << Tutorial_VERSION_MAJOR<< "."<< Tutorial_VERSION_MINOR << std::endl;std::cout << "Usage: " << argv[0] << " number" << std::endl;return 1;}// convert input to doubleconst double inputValue = std::stod(argv[1]);const double outputValue = std::sqrt(inputValue);std::cout << "The square root of " << inputValue << " is " << outputValue << std::endl;return 0;
}

3.2 编写CMake构建脚本

在我们的根目录下的CMakeLists.txt中编写构建命令,如下所示:

cmake_minimum_required(VERSION 3.10)
# set the project name and version
project(Tutorial VERSION 1.0)# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
# configure a header file to pass some of the CMake settings
# to the source code
configure_file(TutorialConfig.h.in TutorialConfig.h)
# add the executable
add_executable(Tutorial tutorial.cxx)
# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
target_include_directories(Tutorial PUBLIC"${PROJECT_BINARY_DIR}")

进入我们之前创建的build目录下,输入如下命令编译项目:

cmake ..
cmake --build .

编译结果:
在这里插入图片描述

然后输入命令:Tutorial.exe 100 运行结果如下:

在这里插入图片描述

在这个开平方根的例子中,我们使用了系统标准库中的std::sqrt(x),我们首先从控制台获取用户输入的值并做合法性验证,最后输出开平方根后的值。
#include "TutorialConfig.h" 这个头文件是编译动态生成的,我们在CMakeLists,txt 中会配置,也就是结合TutorialConfig.h.in文件生成的,让我们在项目中可以使用Tutorial_VERSION_MAJOR这个我们在CMakeLists.txt中配置的值

4.使用子模块实现求平方根的功能

上面的内容演示了如何实现求平方的功能,如我们所见,源代码都放在了根目录下,项目很小的时候这样做也没啥问题,但是当项目变得很大的时候,大量的源代码会让维护变得困难并且CMake构建脚本会变得很复杂,为了解决这个问题,我们可以使用子模块的方法,就是把整个项目的功能拆分成一个个的子模块,在根目录下只负责整合编译构建这些子模块,每个子模块拥有自己的编译脚本,负责本模块的编译工作。这样的话,整个项目的可维护性就变好了,接下来我们看下如何在项目中使用CMake工具添加子模块,这里需要用到一个新的CMake命令:add_subdirectory(子模块目录)

4.1 在子模块中实现两种求平方根的方法

在我们新建的子模块MathFunctions中,我们实现两种求平方根的方法,一种是使用标准库中的std::sqrt(x)函数,另外一种是自己编写算法实现,如下所示:
(1) 使用标准库中的sqrt函数

MathFunctions.h

#pragma once
namespace mathfunctions {
double sqrt(double x);
}

MathFunctions.cpp

#include "MathFunctions.h"
#include <cmath>
namespace mathfunctions {
double sqrt(double x)
{return std::sqrt(x);
}
}

(2)使用自己编写的函数mysqrt

mysqrt.h

#pragma once
namespace mathfunctions {
namespace detail {
double mysqrt(double x);
}
}

mysqrt.cpp

#include "mysqrt.h"
#include <iostream>
namespace mathfunctions {
namespace detail {
double mysqrt(double x)
{if (x <= 0) {return 0;}double result = x;// do ten iterationsfor (int i = 0; i < 10; ++i) {if (result <= 0) {result = 0.1;}double delta = x - (result * result);result = result + 0.5 * delta / result;std::cout << "Computing sqrt of " << x << " to be " << result << std::endl;}return result;
}
}
}

将上面的源代码放到子模块Mathfunctions下,如下所示:
在这里插入图片描述
说明:c++的文件后缀.cxx 和.cpp都是表示C++文件。

4.2 构建Mathfunctions子模块

在MathFunctions的CMakeLIsts.txt中编写构建命令,将MathFunctions子模块构建成一个库,供其他模块使用。只需要使用add_library()命令就可以将当前目录下的C++文件构建成一个库,如下所示:

add_library(MathFunctions MathFunctions.cxx mysqrt.cxx)

上面的代码中将MathFunctions.cxx mysqrt.cxx两个C++文件的源代码编译构建成一个名为MathFunctions的静态库,如下图所示:
在这里插入图片描述注意:使用静态库时,我们可以不用写名字前面的lib以及后缀.a, 例如本例中的libMathFunctions.a我们使用的时候直接用MathFunctions就可以引用到这个库了

后面可以通过这个名字引用这个库,进而可以使用这个库中的功能。

4.3 在根目录引用子模块的功能

当我们把子模块编译构建好了后,就会生成一个名为MathFunctions的库,接下来我们就在根目录下引用这个库实现求平方根的功能。

4.3.1 编写构建脚本

首先是在构建脚本CMakeLists.txt中引入这个库,如下所示:

cmake_minimum_required(VERSION 3.10)
# set the project name and version
project(Tutorial VERSION 1.0)# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)# configure a header file to pass some of the CMake settings
# to the source code
configure_file(TutorialConfig.h.in TutorialConfig.h)# Use add_subdirectory() to add MathFunctions to this project
add_subdirectory(MathFunctions)
# add the executable
add_executable(Tutorial tutorial.cxx)
# Use target_link_libraries to link the library to our executable
target_link_libraries(Tutorial PUBLIC MathFunctions)
# Add MathFunctions to Tutorial's target_include_directories()
# Hint: ${PROJECT_SOURCE_DIR} is a path to the project source.AKA This folder!
# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
target_include_directories(Tutorial PUBLIC"${PROJECT_BINARY_DIR}""${PROJECT_SOURCE_DIR}/MathFunctions")

如上面的代码所示,我们使用add_subdirectory(MathFunctions)将子模块MathFunctions添加到我们的根目录下,在其他模块中想要引用MathFunctions的功能也是这么做,接下来编译整个项目的时候,会连子模块MathFuncitons一起编译,这时会生成静态库libMathFunctions.a文件,我们使用target_link_libraries(Tutorial PUBLIC MathFunctions) 命令来链接我们的静态库,然后再将子模块中的路径配置到根目录的CMakeLists.txt中,方便找到对应的文件,使用的命令是:

target_include_directories(Tutorial PUBLIC"${PROJECT_BINARY_DIR}""${PROJECT_SOURCE_DIR}/MathFunctions")

4.3.2 编写C++代码使用MathFunctions库中提供的功能

在根目录的tutorial.cxx文件中修改求平方根的代码,使用MathFunctions库中提供的功能。如下所示:

#include <cmath>
#include <iostream>
#include <string>#include "TutorialConfig.h"
#include "MathFunctions.h"int main(int argc, char* argv[])
{if (argc < 2) {// report versionstd::cout << argv[0] << " Version " << Tutorial_VERSION_MAJOR << "."<< Tutorial_VERSION_MINOR << std::endl;std::cout << "Usage: " << argv[0] << " number" << std::endl;return 1;}// convert input to doubleconst double inputValue = std::stod(argv[1]);const double outputValue = mathfunctions::sqrt(inputValue);std::cout << "The square root of " << inputValue << " is " << outputValue<< std::endl;return 0;
}

上面的代码中我们使用了#include "MathFunctions.h" 引入了在子模块中定义的头文件,然后使用const double outputValue = mathfunctions::sqrt(inputValue); 求取平方根的值

4.4 运行

在本节的开头提到使用两种方法求平方根,一种是标准库中的,一种是我们自己实现的,如果我们要在这两种方法中切换,有两种办法,一种是修改调用方的代码,即修改Turtorial.cxx中的代码,这种做法通常不可取,因为如果我们提供功能需要调用方去做很多修改的话,还不如不提供。所以这种方法不可取,另外一种方法是在库里面修改,给调用方提供一个接口。
在本项目中,我们需要修改的地方就是子模块的MathFunctions.cxx文件:

#include "MathFunctions.h"
#include <cmath>
#include "mysqrt.h"namespace mathfunctions {
double sqrt(double x)
{// return detail::mysqrt(x); // 使用自定义的函数return std::sqrt(x); // 使用标准库中的函数
}
}

我们可以修改上面的代码,构建项目。运行结果如下:
(1)使用标准库中的函数:
在这里插入图片描述
(2)使用自定义的函数

在这里插入图片描述

5 添加可调试的功能

在上面的内容中介绍了添加子模块的库并调用库中的功能,本节就是本文的主要内容,实现开发环境和生产环境的分离。如此一来,开发者就可以自己选择是使用系统提供的求平方根函数还是使用自己实现的函数。这种方式特别适合用于调试程序。实现这一个功能可以使用CMake提供的option()命令,使用这个命令,开发者可以定义一个变量去表示开发或者是生产环境,或者是使用某个库的标志,设置了后我们就可以通过改变我们定义的变量的值去动态切换我们的函数实现了。

5.1 编写子模块的CMakeLists.txt

要实现可动态切换我们的函数实现的功能,需要我们在构建脚本中编写对应的代码,步骤如下:
首先,使用option()命令定义一个变量USE_MYMATH,并设置其值为ON,表示使用我们自定义的函数,为OFF则使用系统提供的函数

// MathFunctions/CMakeLists.txt
option(USE_MYMATH "use my math implementation" ON)

然后我们使用if()语句来判断USE_MYMATH 的值,并且使用target_compile_definitions()命令来开启或者关闭我们的源代码编译选项。如下所示:

// MathFunctions/CMakeLists.txt
if(USE_MYMATH)target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
endif()

这时候的MathFunctions/CMakeLists.txt就如下所示:

// MathFunctions/CMakeLists.txt
option(USE_MYMATH "use my math implementation" ON)
add_library(MathFunctions MathFunctions.cxx mysqrt.cxx)if(USE_MYMATH)target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")endif()

上面的代码中我们会发现如果USE_MYMATH的值变成了OFF,即不需要使用我们自定义的函数时,本不应该再让mysqrt.cxx文件参与编译,因为我们根本没使用它的功能,这里可以将我们自己实现的功能单独编译成一个静态库,并且在if()语句块里面链接这个库,代码如下所示:

// MathFunctions/CMakeLists.txt
option(USE_MYMATH "use my math implementation" ON)
add_library(MathFunctions MathFunctions.cxx)add_library(SqrtLibrary STATIC mysqrt.cxx)if(USE_MYMATH)target_link_libraries(MathFunctions PRIVATE SqrtLibrary)target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
endif()

至此,我们的CMake构建脚本就编写好了

5.2 在C++代码中使用USE_MYMATH变量

要想实现动态切换我们的两种开平方根功能的实现,仅仅编写构建脚本还不够,构建脚本只能告诉编译器该构建哪些文件,具体需要实现的功能还是得修改具体的实现代码,在C++代码中,我们可以直接使用我们定义的USE_MYMATH变量来觉得使用的实现函数。如下所示:

#include "MathFunctions.h"
#include <cmath>
#ifdef USE_MYMATH#include "mysqrt.h"
#endifnamespace mathfunctions {
double sqrt(double x)
{#ifdef USE_MYMATHreturn detail::mysqrt(x); // 使用自定义的函数#elsereturn std::sqrt(x); // 使用标准库中的函数#endif
}
}

如上面的代码所示,如果我们定义了USE_MYMATH这个变量,那么就使用自定义的求平方根函数,否则使用标准库的求平方根函数

5.3 运行代码

当代码都编写完成后,我们可以进入build目录,然后使用命令:

cmake ..

编译项目,编译完成后使用:

cmake --build .

构建项目,构建玩成后使用下面命令运行项目:

Tutorial.exe 100

这时,因为默认的实现时使用我们自定义的求平方根方法,假如我们想切换求平方根的方法为系统提供的,则运行下面的命令:

cmake .. -DUSE_MYMATH=OFF

然后再运行:

cmake --build .

构建项目,接着运行:

Tutorial.exe 100

查看运行结果,所有的运行结果如下所示:
在这里插入图片描述
至此,本文结束。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/173071.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

qt高精度定时器的使用停止线程应用

##线程停止 //线程停止应用 public: explicit WorkerThread(QObject *parent 0) :QThread(parent), m_bStopped(false){qDebug() << "Worker Thread : " << QThread::currentThreadId();}~WorkerThread(){stop();quit();wait();}void stop() {qDebug()…

队列(Queue)概念+通过单、双链表来模拟队列+环形队列+OJ面试题(用队列实现栈、用栈实现队列、设计环形队列)

文章目录 队列(Queue)一、 概念1.尾进头出 二、模拟队列1.单链表实现队列1.1 设置结点1.2 入队offer1.3出队 poll1.4 empty方法&#xff0c;peek方法&#xff0c;getUsedSize方法 2.双链表实现队列2.1 创建结点2.2 入队列2.3 出队列2.4 peek、size、isEmpty方法 三、环形队列1.…

小程序源文件的简单获取方法分享

小程序的源文件地址 在微信的服务器上。普通用户想要直接获取到在微信服务器去获取,肯定是十分困难的,有没有别的办法呢? 简单思考一下我们使用小程序的场景就会明白,当我们点开一个微信小程序的时候,其实是微信已经将它的从服务器上下载到了手机,然后再来运行的。所以我…

Android:窗口管理器WindowManager

Android&#xff1a;窗口管理器WindowManager 导言 本篇文章主要是对Android中与窗口(Window)有关的知识的介绍&#xff0c;主要涉及到的有&#xff1a; WindowWindowManagerWindowManagerService 主要是为了更进一步地向下地深入Android屏幕渲染的知识&#xff08;虽然窗口…

打破尺寸记录!荷兰QuTech研发16量子点阵列新技术

承载16个量子点交叉条阵列的量子芯片&#xff0c;可无缝集成到棋盘图案&#xff08;图片来源&#xff1a;网络&#xff09; 由荷兰代尔夫特理工大学(TU Delft)和荷兰应用科学研究组织(TNO)组建的荷兰量子计算研究中心QuTech的研究人员开发了一种用相对较少的控制线来控制大量量…

Python 算法高级篇:图的表示与存储优化

Python 算法高级篇&#xff1a;图的表示与存储优化 引言 1. 什么是图&#xff1f;2. 图的基本概念3. 图的表示方法3.1. 临接矩阵表示临接矩阵的优点&#xff1a;临接矩阵的缺点&#xff1a; 3.2. 邻接表表示邻接表的优点&#xff1a;邻接表的缺点&#xff1a; 4. 优化的存储方法…

【C++笔记】C++继承

【C笔记】C继承 一、继承的概念二、继承的语法和权限三、父类和子类成员之间的关系3.1、子类赋值给父类(切片)3.2、同名成员 四、子类中的默认成员函数4.1、构造函数4.2、拷贝构造4.3、析构函数 五、C继承大坑之“菱形继承”5.1、什么是“菱形继承”5.2、解决方法 一、继承的概…

C++深度优化(DFS)算法的应用:收集所有金币可获得的最大积分

涉及知识点 深度优化(DFS) 记忆化 题目 节点 0 处现有一棵由 n 个节点组成的无向树&#xff0c;节点编号从 0 到 n - 1 。给你一个长度为 n - 1 的二维 整数 数组 edges &#xff0c;其中 edges[i] [ai, bi] 表示在树上的节点 ai 和 bi 之间存在一条边。另给你一个下标从 0…

ArcGIS笔记13_利用ArcGIS制作岸线与水深地形数据?建立水动力模型之前的数据收集与处理?

本文目录 前言Step 1 岸线数据Step 2 水深地形数据Step 3 其他数据及资料 前言 在利用MIKE建立水动力模型&#xff08;详见【MIKE水动力笔记】系列&#xff09;之前&#xff0c;需要收集、处理和制作诸多数据和资料&#xff0c;主要有岸线数据、水深地形数据、开边界潮位驱动数…

位(bit)、字节(byte)、字、英文字符、中文字符的关系详解(涵盖字符编码)

目录 0 引言1 位、字节、字2 字符编码2.1 为什么要有字符编码2.2 字符编码的种类有哪些拓展&#xff1a;ANSI 编码 3 英文字符与中文字符的区别 &#x1f64b;‍♂️ 作者&#xff1a;海码007&#x1f4dc; 专栏&#xff1a;C专栏&#x1f4a5; 标题&#xff1a;位&#xff08;…

至高直降3000元,微星笔记本双11爆款推荐、好评有礼拿到手软

今年双11来的更早一些&#xff0c;微星笔记本先行的第一波雷影17促销活动&#xff0c;就已经领略到玩家们满满的热情。开门红高潮一触即发&#xff0c;微星笔记本双11活动周期至高直降3000元&#xff0c;众多爆款好货已经开启预约预售&#xff1a;有硬核玩家偏爱的性能双雄&…

聚观早报 |2024款飞凡R7官宣;小米14新配色材质

【聚观365】10月27日消息 2024款飞凡R7官宣 小米14新配色材质 金山办公2023第三季度业绩 IBM2023第三季度业绩 新东方2024财年第一季度业绩 2024款飞凡R7官宣 飞凡汽车官宣&#xff0c;2024款飞凡R7将于11月上市&#xff0c;新车将搭载飞凡巴赫座舱&#xff0c;同时超过1…

Node编写重置用户密码接口

目录 前言 定义路由和处理函数 验证表单数据 实现重置密码功能 前言 接前面文章&#xff0c;本文介绍如何编写重置用户密码接口 定义路由和处理函数 路由 // 重置密码的路由 router.post(/updatepwd, userinfo_handler.updatePassword) 处理函数 exports.updatePasswo…

php之 角色的权限管理(RBAC)详解

RBAC&#xff08;Role-based access control&#xff09;是一种常见的权限管理模型&#xff0c;通过将用户分配至特定的角色&#xff0c;以及为角色分配访问权限&#xff0c;实现了权限管理的目的。以下是关于RBAC的详细解释&#xff1a; 角色&#xff1a;RBAC模型的核心是角色…

65、内网安全-域环境工作组局域网探针方案

目录 案例1-基本信息收集操作演示案例2-网络信息收集操作演示案例3-用户信息收集操作演示案例4-凭据信息收集操作演示案例5-探针主机域控架构服务操作演示涉及资源 我们攻击内网一般是借助web攻击&#xff0c;直接进去&#xff0c;然后再去攻击内网&#xff0c;那么攻击的对象一…

搞懂 MySql 的架构和执行流程

搞懂 MySql 的架构和执行流程 1、MySQL 的三层架构2、SQL 的执行流程2.1、连接器2.2、解析器2.3、预处理器2.4、优化器2.5、执行器2.6、存储引擎 3、关于Select 的两个顺序 1、MySQL 的三层架构 MySQL的三层结构包括&#xff1a; 连接层&#xff1a;负责与MySQL客户端之间的通…

matlab中类的分别之handle类和value类——matlab无法修改类属性值的可能原因

写在之前&#xff08;吐槽&#xff09; 最近由于变化了一些工作方向&#xff0c;开始需要使用matlab进行开发&#xff0c;哎哟喂&#xff0c;matlab使用的我想吐&#xff0c;那个matlab编辑器又没代码提示&#xff0c;又没彩色&#xff0c;我只好用vscode进行代码编辑&#xf…

13.6性能测试理论

一.什么是性能测试 1.定义: 测试人员借助性能测试工具(LoadRunner等),模拟系统在不同场景下(使用高峰期等),对应的性能指标是否达到预期. 2.性能测试和功能测试的区别: a.功能测试依靠人工,性能测试依靠工具. b)功能测试要求软件能正常运行,不管什么场景,性能测试要求软件…

嵌入式 Tomcat 调校

SpringBoot 嵌入了 Web 容器如 Tomcat/Jetty/Undertow&#xff0c;——这是怎么做到的&#xff1f;我们以 Tomcat 为例子&#xff0c;尝试调用嵌入式 Tomcat。 调用嵌入式 Tomcat&#xff0c;如果按照默认去启动&#xff0c;一个 main 函数就可以了。 简单的例子 下面是启动…

故障诊断入门书籍资料免费领取

前言 本期分享免费提供9本故障诊断领域相关的书籍资料&#xff0c;可自行下载 一、主要内容 二、书籍获取