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

news2025/1/12 22:51:45

目录

  • 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 version
    std::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 double
  const 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 iterations
  for (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 version
    std::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 double
  const 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"
#endif

namespace mathfunctions {
double sqrt(double x)
{
  #ifdef USE_MYMATH
    return detail::mysqrt(x); // 使用自定义的函数
  #else
    return 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.coloradmin.cn/o/1146578.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CNCC2023

中国工程院院士&#xff0c;之江实验室主任、阿里云创始人王坚&#xff1a;计算驱动的科学发现和科技创新。 国际计算机学会主席雅尼斯约阿尼迪斯(ACM President Yannis Ioannidis)。 电气和电子工程师协会计算机协会主席妮塔帕特尔(IEEE CS President Nita Patel)。 2022 I…

Python武器库开发-高级特性篇(九)

高级特性篇(九) 切片 在Python中&#xff0c;切片(slice)是对序列型对象(如list, string, tuple)的一种高级索引方法。普通索引只取出序列中一个下标对应的元素&#xff0c;而切片取出序列中一个范围对应的元素&#xff0c;这里的范围不是狭义上的连续片段。 要创建切片&…

SpringMVC系列-6 异常处理器

背景 本文作为 SpringMVC系列 的第六篇&#xff0c;介绍SpringMVC的异常处理器。内容包括异常处理器的使用方式、实现原理和内置异常处理器的装配过程。 1.使用方式 自定义异常类&#xff0c;用于异常处理器&#xff1a; public class ClientException extends RuntimeExce…

宝塔安装MySQL数据库,并内网穿透实现公网远程访问

宝塔安装MySQL数据库&#xff0c;并内网穿透实现公网远程访问 文章目录 宝塔安装MySQL数据库&#xff0c;并内网穿透实现公网远程访问前言1.Mysql服务安装2.创建数据库3.安装cpolar3.2 创建HTTP隧道 4.远程连接5.固定TCP地址5.1 保留一个固定的公网TCP端口地址5.2 配置固定公网…

操作系统——页表、快表 + 地址转化过程(王道视频p47、p48、p49)

1.页表的基本概念&#xff1a; &#xff08;1&#xff09;part1: &#xff08;2&#xff09;part2: (备注&#xff1a;默认——逻辑页 和 物理页 的页大小是相同的&#xff01;&#xff01;&#xff01;) 2.在没有 快表的case下&#xff0c;进行逻辑地址 、 物理地址转换的流…

如何使用Codesys编程

在实际项目中&#xff0c;使用了两套TLC6伺服系统&#xff0c;不仅是因为TLC6具有电子凸轮功能&#xff0c;而且TLC6伺服驱动器内置PLC&#xff0c;使实现系统功能的编程更简单方便&#xff0c;且由于是内部编程&#xff0c;使系统响应更迅速。 TLC6内置PLC的编程环境为符合IEC…

C语言--输出格式控制(printf函数)

格式输出函数printf printf(格式控制&#xff0c;输出表列) 基本用法 格式字符功能例子d输出一个有符号的十进制整数printf("%d %d",12,-56);c输出一个字符 char ch a; printf("%c",ch); s输出一个字符串printf("%s","oh my god&…

Go学习第十六章——Gin文件上传与下载

Go web框架——Gin文件上传与下载 1. 文件上传1.1 入门案例&#xff08;单文件&#xff09;1.2 服务端保存文件的几种方式SaveUploadedFileCreateCopy 1.3 读取上传的文件1.4 多文件上传 2. 文件下载2.1 快速入门2.2 前后端模式下的文件下载2.3 中文乱码问题 1. 文件上传 1.1 …

年度最佳榜单之 10 大免费数据恢复软件

我们经常会因为不小心从硬盘驱动器、SD 卡和数码相机等存储设备中删除重要数据而丢失它们。还有许多其他原因&#xff0c;如格式化、病毒攻击和不当操作都可能导致数据丢失。在本文中&#xff0c;我向您推荐年度最佳数据恢复软件列表&#xff0c;以帮助恢复丢失的数据。 10 大免…

强大易于编辑的流程图组织图绘制工具draw.io Mac苹果中文版

draw.io可以绘制多种类型的图表&#xff0c;包括但不限于流程图、组织结构图、网络图、UML图、电气工程图等。draw.io提供了丰富的图形元素和编辑功能&#xff0c;使用户能够轻松地创建和编辑各种复杂的图表。同时&#xff0c;该软件还支持多种导出格式&#xff0c;方便用户在不…

Openssl数据安全传输平台017:客户端在Linux上的编译与调试

客户端代码在widows上编译&#xff0c;除了protobuf找不到目录&#xff0c;其他的基本没有什么问题。 然后打开虚拟机&#xff0c;项目文件已经在/home/projects目录下了 进入项目文件&#xff0c;对代码进行编译 第一次 // 找不到protobuf g *.cpp *.cc -ljson -lpthread -…

计算机中了locked勒索病毒怎么办,locked勒索病毒解密,数据恢复

当下网络技术飞速发展&#xff0c;但同样带来的网络安全威胁也不断增加&#xff0c;其中较为明显的威胁就是locked勒索病毒&#xff0c;自从今年以来&#xff0c;很多企业的计算机都遭受到了locked勒索病毒攻击&#xff0c;导致企业的计算机系统瘫痪。通过云天数据恢复工程师对…

【LeetCode每日一题合集】2023.10.23-2023.10.29(简单的一周)

文章目录 2678. 老人的数目&#xff08;简单遍历模拟&#xff09;1155. 掷骰子等于目标和的方法数&#xff08;动态规划&#xff09;2698. 求一个整数的惩罚数&#xff08;预处理dfs回溯&#xff09;2520. 统计能整除数字的位数&#xff08;简单模拟&#xff09;1465. 切割后面…

✨✨✨if __name__ == “__main__“ 在 Python 中意味着什么?

✨✨✨if __name__ "__main__" 在 Python 中意味着什么&#xff1f; 1. 原理2. 总结参考 在阅读其他人的代码时&#xff0c;你可能遇到过 Python 的 if__name__ “main” 习惯用法。这篇博客将了解有关 Python if name “main” 习惯用法的所有信息。 1. 原理 这…

linux 模块安装与卸载

文章目录 模块实现编译模块的 makefile编译报错解决模块编译日志自动化模块安装模块卸载 模块实现 新建 my_module.c 文件 #include <linux/types.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/module.h>static int __ini…

python随手小练8(南农作业题)

题目1: 输入3 门课程 a,b,c 的成绩,求 3 门成绩的总和平均值(整数,四舍五人)以及最高和最低值。如果3门课程考试成绩分别以权重 0.50.3 和0.2计人总评成绩(整数先求总和再四舍五入),则最终总评成绩是多少? 具体操作&#xff1a; a float(input("a:")) b float(in…

基于Java的体育竞赛成绩管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09; 代码参考数据库参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&am…

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

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

JEnv使用初体验

Java多版本控制器初体验 1、前言 由于公司项目使用jdk8版本&#xff0c;而日常学习会使用其他版本例如jdk17等&#xff0c;往常都是修改环境配置目录实现。 2、下载资料 链接&#xff1a;https://pan.baidu.com/s/1UqzHv8K8WBu-75Ysyc_h3A 提取码&#xff1a;ra6a 3、安装 …

Java中的类你了解多少(每日一练)

文章目录 类的加载方式类的加载流程类的生命周期加载验证准备解析初始化类的销毁 类加载器有哪些什么是双亲委派模型&#xff1f; 类的加载方式 开发工具可以将java源代码编译为class字节码&#xff0c;类加载器加载class字节码&#xff0c;将字节码中的内容分配到内存中&#…