CMake在Windows环境下Visual Studio Code的使用
在Windows环境下,使用CMake可以帮助我们更方便地管理和构建C++项目。而在使用CMake的过程中,我们可以使用任何一个编辑器,包括VSCode,来编辑和构建我们的代码。
本文将介绍如何在Windows环境下使用VSCode编辑器和CMake构建C++项目,包括从最简单的单文件工程到多文件、多子文件夹的工程如何使用CMake,以及如何添加第三方库。
推荐的Cmake教程:苏丙榅 Cmake教程
CMake构建
前置要求
在开始之前,您需要安装以下软件:
- Visual Studio Code及插件
- CMake
- C++编译器
安装配置CMake
选择一个后缀为.msi的Windows安装包下载,尽量选最新的
安装时记得勾选“Add CMake to the system PATH for all users”,这样就不用自己再配置环境变量了
安装后在cmd输入以下目录验证是否安装成功
cmake -version
安装配置MinGw
- MinGw中包含gcc,g++等多种编译器,可以在windows上使用(windows无法直接安装gcc),下载链接:sourceforge
解压时尽量解压在某个盘的根目录。 - 将MinGW下bin目录的路径添加到环境变量中
C:\mingw64\bin
-
进入bin文件夹,找到
mingw32-make.exe
,复制一份,将其中一份重命名为make.exe
(依旧保存在bin文件夹中) -
验证是否配置成功,cmd中输入
gcc -v make -v
可以判断是否安装成功
安装VSCode及插件
在vscode官网下载最新安装包,而非便携包
安装时注意勾选以下选型
1:在桌面创建快捷方式:勾选
2:将VSCode添加到右键菜单,支持打开文件:勾选
3:将VSCode添加到右键菜单,支持打开目录:勾选
4:勾选后会把很多文本格式改为用VSCode打开,勾选
5:添加到PATH(环境变量),自动添加,无需手动配置:勾选
安装完成后打开VScode,安装一个插件C/C++ Extension Pack
以下4个插件均为其依赖
单文件工程
在本节中,我们将创建一个简单的单文件工程,并使用CMake构建它。
创建项目
首先,我们需要创建一个文件夹,将其命名为hello-world
。在该文件夹中,创建一个名为main.cpp
的文件,并将以下内容复制到其中:
#include <iostream>
int main()
{
std::cout << "Hello, world!" << std::endl;
return 0;
}
创建CMakeLists.txt文件
接下来,我们需要在hello-world
文件夹中创建一个名为CMakeLists.txt
的文件,并将以下内容复制到其中:
cmake_minimum_required(VERSION 3.10)
project(hello-world)
add_executable(hello-world main.cpp)
该文件告诉CMake如何构建我们的项目。首先,我们指定了CMake的最低版本为3.10。然后,我们指定了项目名称为hello-world
。最后,我们使用add_executable
命令将main.cpp
文件添加到我们的项目中。
构建项目
A:使用VSCode插件构建
接下来,我们需要使用CMake插件构建我们的项目。
打开VSCode,将文件夹hello-world
打开为工作区。
按下ctrl+shift+P
,输入以下命令并回车,选择电脑上安装好的编译器即可完成配置
CMake: configure
配置完成后会在目录下生成一个build文件夹
随后按下F7
,或按下ctrl+shift+P
,输入以下命令并回车即可完成编译
Cmake: build
该命令将会编译并链接我们的程序,最终生成一个名为hello-world
的可执行文件。
最后,我们可以在终端中运行该可执行文件:
./build/hello-world
B:使用命令行cmake构建
- 手动创建build文件夹
mkdir build
- 进入至build文件夹中
cd build
- cmake 配置
如果已安装了VS,可能会调用MS的MSVC编译器,使用下面命令来代替 cmake ..
即可
cmake -G "MinGW Makefiles" ..
只有第一次使用cmake时使用以上命令,后续便可直接使用
cmake ..
- 编译文件
make
该命令将会编译并链接我们的程序,最终生成一个名为hello-world
的可执行文件。
- 运行exe
最后,我们可以在终端中运行该可执行文件:
./hello-world
结果
当我们运行hello-world
可执行文件时,将会输出以下内容:
Hello, world!
多文件工程
在本节中,我们将创建一个包含多个文件的工程,并使用CMake构建它。
创建项目
首先,我们需要创建一个文件夹,将其命名为multi-file
。在该文件夹中,创建一个名为main.cpp
的文件,并将以下内容复制到其中:
#include <iostream>
#include "hello.hpp"
int main()
{
std::cout << hello() << std::endl;
return
}
在同一个文件夹中,创建一个名为hello.cpp
的文件,并将以下内容复制到其中:
#include "hello.hpp"
std::string hello()
{
return "Hello, world!";
}
还需在同一文件夹中,创建一个名为hello.hpp
的文件,并将以下内容复制到其中:
#pragma once
#include <string>
std::string hello();
创建CMakeLists.txt文件
接下来,我们需要在multi-file
文件夹中创建一个名为CMakeLists.txt
的文件,并将以下内容复制到其中:
cmake_minimum_required(VERSION 3.10)
project(multi-file)
add_executable(multi-file main.cpp hello.cpp)
该文件与单文件工程的文件类似,我们添加了hello.cpp
文件并将其与main.cpp
一起添加到我们的项目中。
构建项目
接下来,我们需要使用CMake构建我们的项目。打开VSCode,将文件夹multi-file
打开为工作区。
按下ctrl+shift+P
,输入以下命令并回车即可完成配置
CMake: configure
配置完成后会在目录下生成一个build文件夹
随后按下F7
,或按下ctrl+shift+P
,输入以下命令并回车即可完成编译
Cmake: build
最后,我们可以在终端中运行该可执行文件:
./build/multi-file
结果
当我们运行multi-file
可执行文件时,将会输出以下内容:
Hello, world!
多子文件夹工程
在本节中,我们将创建一个包含多个子文件夹的工程,并使用CMake构建它。
创建项目
首先,我们需要创建一个名为multi-folder
的文件夹。在该文件夹中,创建一个名为main.cpp
的文件,并将以下内容复制到其中:
#include <iostream>
#include "hello.hpp"
int main()
{
std::cout << hello() << std::endl;
return 0;
}
然后,创建一个名为hello
的子文件夹,在该子文件夹中,创建一个名为hello.cpp
的文件,并将以下内容复制到其中:
#include "hello.hpp"
std::string hello()
{
return "Hello, world!";
}
还需在hello
子文件夹中,创建一个名为hello.hpp
的文件,并将以下内容复制到其中:
#pragma once
#include <string>
std::string hello();
创建CMakeLists.txt文件
接下来,我们需要在multi-folder
文件夹中创建一个名为CMakeLists.txt
的文件,并将以下内容复制到其中:
cmake_minimum_required(VERSION 3.10)
project(multi-folder)
add_subdirectory(hello)
add_executable(multi-folder main.cpp)
target_link_libraries(multi-folder hello)
该文件包含了以下几个步骤:
- 我们使用
add_subdirectory
命令将hello
子文件夹添加到我们的项目中。 - 我们使用
add_executable
命令将main.cpp
文件添加到我们的项目中。 - 我们使用
target_link_libraries
命令将multi-folder
可执行文件链接到hello
库。
创建子文件夹CMakeLists.txt文件
在hello
子文件夹中,我们还需要创建一个名为CMakeLists.txt
的文件,并将以下内容复制到其中:
add_library(hello hello.cpp)
target_include_directories(hello PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
该文件包含了以下几个步骤:
- 我们使用
add_library
命令将hello.cpp
文件编译为一个库。 - 我们使用
target_include_directories
命令将hello
库的头文件路径添加到编译器的包含路径中。
构建项目
接下来,我们需要使用CMake构建我们的项目。
打开VSCode,按下ctrl+shift+P
,输入以下命令并回车即可完成配置
CMake: configure
配置完成后会在目录下生成一个build文件夹
随后按下F7
,或按下ctrl+shift+P
,输入以下命令并回车即可完成编译
CMake: build
最后,我们可以在终端中运行该可执行文件:
./build/multi-folder
结果
当我们运行multi-folder
可执行文件时,将会输出以下内容:
Hello, world!
添加第三方库
在本节中,我们将介绍如何在我们的项目中添加第三方库。
下载第三方库
我们将使用一个名为fmt
的库作为示例。fmt
是一个格式化字符串库,可以帮助我们轻松地格式化输出。首先,我们需要从fmt
的GitHub页面上下载该库。在终端中,输入以下命令:
git clone https://github.com/fmtlib/fmt.git
这将在当前目录中创建一个名为fmt
的文件夹。
创建项目
我们将使用之前创建的multi-file
项目作为示例。在该项目中,我们将添加fmt
库并使用它格式化输出。
将库添加到项目中
将fmt
库添加到我们的项目中有几种不同的方法。
一种方法是将fmt
库的源代码直接添加到我们的项目中。
但是,在这里,我们将使用另一种方法。我们将使用CMake的ExternalProject
模块来构建fmt
库,并将其作为依赖项添加到我们的项目中。
在multi-file
项目的根目录中,创建一个名为cmake
的文件夹,并在其中创建一个名为ExternalProject
的文件夹。在ExternalProject
文件夹中,创建一个名为CMakeLists.txt
的文件,并将以下内容复制到其中:
include(ExternalProject)
set(FMT_ROOT ${CMAKE_BINARY_DIR}/fmt)
ExternalProject_Add(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG master
PREFIX ${FMT_ROOT}
INSTALL_COMMAND ""
)
add_library(fmt INTERFACE)
target_include_directories(fmt INTERFACE ${FMT_ROOT}/src/fmt/include)
add_dependencies(fmt fmt)
该文件包含了以下几个步骤:
-
我们使用
ExternalProject_Add
命令将fmt
库添加到我们的项目中。在该命令中,我们指定了该库的Git仓库地址和版本号,以及该库的安装路径。 -
我们使用
add_library
命令将一个名为fmt
的接口库添加到我们的项目中。该库不会包含任何源代码,而是包含fmt
库的头文件路径。 -
我们使用
target_include_directories
命令将fmt
库的头文件路径添加到fmt
库的接口库中。 -
我们使用
add_dependencies
命令将fmt
库添加到我们的项目中的依赖项列表中。
修改CMakeLists.txt文件
接下来,我们需要修改multi-file
项目的CMakeLists.txt
文件。我们需要将fmt
库添加到我们的项目中,并使用它格式化输出。我们将添加以下内容到该文件中:
# 添加fmt库
add_subdirectory(cmake/ExternalProject)
include_directories(${FMT_ROOT}/src/fmt/include)
# 将hello库添加到项目中
add_subdirectory(hello)
add_executable(multi-folder main.cpp)
# 链接hello库
target_link_libraries(multi-folder hello)
# 链接fmt库
target_link_libraries(multi-folder fmt::fmt)
该文件包含了以下几个步骤:
- 我们使用
add_subdirectory
命令将ExternalProject
文件夹添加到我们的项目中。 - 我们使用
include_directories
命令将fmt
库的头文件路径添加到编译器的包含路径中。 - 我们使用
add_executable
命令将main.cpp
文件编译为可执行文件。 - 我们使用
target_link_libraries
命令将hello
库和fmt
库链接到multi-folder
可执行文件中。
构建项目
接下来,我们需要使用CMake构建我们的项目。
打开VSCode,按下ctrl+shift+P
,输入以下命令并回车即可完成配置
CMake: configure
配置完成后会在目录下生成一个build文件夹
随后按下F7
,或按下ctrl+shift+P
,输入以下命令并回车即可完成编译
CMake: build
结果
当我们运行multi-folder
可执行文件时,将会输出以下内容:
Hello, world! The answer is 42.
该输出中包含了我们使用fmt
库格式化的内容。
实例:以第三库方式使用CSerialPort
CSerialPort项目是基于C++的轻量级开源跨平台串口类库,用于实现跨平台多操作系统的串口读写。
CSerialPort已经在以下平台做过测试
- Windows ( x86, x86_64, arm64 )
- Linux ( x86, x86_64, arm, arm64/aarch64, mips64el, riscv, s390x, ppc64le )
- macOS ( x86_64 )
- Raspberry Pi ( armv7l )
- FreeBSD ( x86_64 )
1. 使用cmake生成CSerialPort动态库
git clone https://github.com/itas109/CSerialPort
# 或使用国内码云地址
git clone https://gitee.com/itas109/CSerialPort.git
cd CSerialPort
mkdir bin
cd bin
cmake .. -G "MinGW Makefiles" -DCMAKE_INSTALL_PREFIX=install -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=ON
cmake --build . --config Debug
cmake --install . --config Debug
2. 以cmake方式引用CSerialPort的动态库
通过find_package自动搜索CSerialPort头文件及动态库
CMakeLists.txt
cmake_minimum_required(VERSION 2.8.12)
project(DapensonUart LANGUAGES CXX)
find_package(CSerialPort)
if (CSerialPort_FOUND)
include_directories(${CSerialPort_INCLUDE_DIR})
add_executable( ${PROJECT_NAME} main.cpp)
target_link_libraries (${PROJECT_NAME} ${CSerialPort_LIBRARY})
else()
message(STATUS "Not found system CSerialPort")
endif ()
注意:出现如下错误,可设置CMAKE_PREFIX_PATH指定搜索路径,如
cmake .. -DCMAKE_PREFIX_PATH="D:/CommConsole/CSerialPort/bin/install"
注意,路径需设置为上一步操作中生成的CSerialPort/bin/install
目录
3. 新建项目编译测试
在需要的地方新建一个项目文件夹,
新建文件
文件夹目录如下
CMakeLists.txt
main.cpp
其中CMakeLists.txt内容见上一步骤
main.cpp
#include <iostream>
#ifdef _WIN32
#include <Windows.h>
#define imsleep(microsecond) Sleep(microsecond) // ms
#else
#include <unistd.h>
#define imsleep(microsecond) usleep(1000 * microsecond) // ms
#endif
#include <vector>
#include "CSerialPort/SerialPort.h"
#include "CSerialPort/SerialPortInfo.h"
using namespace itas109;
std::string char2hexstr(const char *str, int len)
{
static const char hexTable[17] = "0123456789ABCDEF";
std::string result;
for (int i = 0; i < len; ++i)
{
result += "0x";
result += hexTable[(unsigned char)str[i] / 16];
result += hexTable[(unsigned char)str[i] % 16];
result += " ";
}
return result;
}
int countRead = 0;
class MyListener : public CSerialPortListener
{
public:
MyListener(CSerialPort *sp)
: p_sp(sp){};
void onReadEvent(const char *portName, unsigned int readBufferLen)
{
if (readBufferLen > 0)
{
char *data = new char[readBufferLen + 1]; // '\0'
if (data)
{
// read
int recLen = p_sp->readData(data, readBufferLen);
if (recLen > 0)
{
data[recLen] = '\0';
std::cout << portName << " - Count: " << ++countRead << ", Length: " << recLen << ", Str: " << data << ", Hex: " << char2hexstr(data, recLen).c_str()
<< std::endl;
// return receive data
// p_sp->writeData(data, recLen);
}
delete[] data;
data = NULL;
}
}
};
private:
CSerialPort *p_sp;
};
int main()
{
CSerialPort sp;
std::cout << "Version: " << sp.getVersion() << std::endl
<< std::endl;
MyListener listener(&sp);
std::vector<SerialPortInfo> m_availablePortsList = CSerialPortInfo::availablePortInfos();
std::cout << "availableFriendlyPorts: " << std::endl;
for (size_t i = 1; i <= m_availablePortsList.size(); ++i)
{
SerialPortInfo serialPortInfo = m_availablePortsList[i - 1];
std::cout << i << " - " << serialPortInfo.portName << " " << serialPortInfo.description << " " << serialPortInfo.hardwareId << std::endl;
}
// 检查是否有可用串口,如果没有则退出
if (m_availablePortsList.size() == 0)
{
std::cout << "No valid port" << std::endl;
return 0;
}
std::cout << std::endl;
const char *portName = "COM10";
std::cout << "Port Name: " << portName << std::endl;
sp.init(portName, // windows:COM1 Linux:/dev/ttyS0
itas109::BaudRate115200, // baudrate
itas109::ParityNone, // parity
itas109::DataBits8, // data bit
itas109::StopOne, // stop bit
itas109::FlowNone, // flow
4096 // read buffer size
);
sp.setReadIntervalTimeout(0); // read interval timeout 0ms
sp.open();
std::cout << "Open " << portName << (sp.isOpen() ? " Success" : " Failed") << std::endl;
// 连接读取事件
sp.connectReadEvent(&listener);
// 第一次写入十六进制数据
char hex[5];
hex[0] = 0x31;
hex[1] = 0x32;
hex[2] = 0x33;
hex[3] = 0x34;
hex[4] = 0x35;
sp.writeData(hex, sizeof(hex));
// 写入字符串数据
sp.writeData("Dapenson", 8);
// 循环等待
for (;;)
{
// 延时1S
imsleep(1000);
// 写入字符串数据
const char *data_send = "Dapenson\n";
sp.writeData(data_send, strlen(data_send));
}
return 0;
}
编译运行
完成以上2个文件新建编辑以后,在文件路径下打开cmd,运行以下命令
mkdir bin
cd bin
cmake .. -G "MinGW Makefiles" -DCMAKE_PREFIX_PATH="D:/CommConsole/CSerialPort/bin/install"
make
运行后即可在文件夹中的bin目录下生成一个main.exe文件,运行即可。
如果报错需要dll,则将install目录下的dll复制到exe同目录即可
至此,就完成了CSerialPort第三方库的demo,可用虚拟串口软件vspd或是串口硬件进行数据收发的回环测试。
总结
本文介绍了如何在Windows环境下,在VSCode编辑器中使用CMake构建项目。我们从最简单的单文件项目开始,并逐步扩展到多文件、多子文件夹的项目,并向其中添加第三方库。
除了本文所介绍的基础使用之外,CMake还有很多其他的特性和用法。下面是一些其他的注意事项和建议:
- CMake支持交叉编译,可以在一个系统上构建并运行另一个不同架构或操作系统的应用程序。
- CMake支持构建多个目标,包括库、可执行文件和测试程序。
- CMake可以集成到许多不同的IDE中,包括Visual Studio、Xcode和Qt Creator等。
- CMake支持许多不同的第三方库,包括Boost、OpenCV和Qt等。在使用这些库时,可以使用
find_package
命令查找和配置它们。 - CMake也支持许多其他的配置选项和变量,可以根据具体需求进行调整。
总之,CMake是一个非常强大和灵活的构建系统,可以帮助开发者轻松地管理和构建项目。学习和掌握CMake的使用,对于开发者来说是非常有用的技能。