如何在VSCode 中采用CMake编译C++程序
文章目录
配置环境
1、如何解决Windows下无法编译std::thread 库的问题
一个简单的确认方法就是在终端中输入 g++ -v,查看输出中的【Thread model: 】中的参数值是否为【posix】,如果是【Win32】 则无法编译 std::thread 库。
cmake : 无法将“cmake”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。 这个错误。
打开 VScode 中的 setting, 找到 Cmake path ,然后将该值修改为 本机电脑上安装的 cmake 路径即可。
3、如果在使用 make 的时候提示如下错误:
make : 无法将“make”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。
将 MinGW64 目录 \bin 下的 mingw32-make.exe 复制一份,然后重命名为 make.exe 就可以解决该问题。
4、配置 VScode 中一些环境以及安装对应的插件
从零开始在Vscode上使用mingw64配置C&C++环境附带美化
单个CPP 文件,不依赖外部库,只使用C++的库
// HelloWorld.cpp
#include <iostream>
int main(int argc, char* argv[])
{
std::cout<<"Hello world"<<std::endl;
return 0;
}
编译该文件
g++ HelloWorld.cpp -lm -o HelloWorld
执行生成的 HelloWorld.exe 文件,输出 Hello World 的字符串
.\HelloWorld.exe
Hello world
两个CPP 文件,不依赖外部库,只使用C++的库
// Test1.h
#include <iostream>
int PrintHelloWorld();
double MyAdd(double a, double b);
// Test1.cpp
#include "Test1.h"
int PrintHelloWorld()
{
std::cout<<"Hello World"<<std::endl;
return 0;
}
double MyAdd(double a, double b)
{
return a + b;
}
// main.cpp
#include "Test1.h"
int main(int argc, char* argv[]) {
PrintHelloWorld();
std::cout<<"a + b = " << MyAdd(10.0, 9.0)<<std::endl;
return 0;
}
编译该文件
g++ Test1.cpp main.cpp -lm -o main
执行生成的 HelloWorld.exe 文件,输出 Hello World 的字符串
./main.exe
Hello World
a + b = 19
三个CPP 文件,不依赖外部库,只使用C++的库
// Test2.h
#include <iostream>
double MyMul(double a, double b);
// Test2.cpp
#include "Test2.h"
#include "Test1.h"
double MyMul(double a, double b)
{
double sum = MyAdd(a, b);
return sum * a;
}
// main.cpp
#include "Test1.h"
#include "Test2.h"
int main(int argc, char* argv[]) {
PrintHelloWorld();
std::cout<<"a + b = " << MyAdd(10.0, 9.0)<<std::endl;
std::cout<<"(a + b) * a = " << MyMul(10.0, 9.0)<<std::endl;
return 0;
}
编译该文件
g++ Test1.cpp Test2.cpp main.cpp -lm -o main
执行生成的 HelloWorld.exe 文件,输出 Hello World 的字符串
./main.exe
Hello World
a + b = 19
(a + b) * a = 190
注意
如果将上述的 Test1.h、 Test1.cpp、 Test2.h、 Test2.cpp 放在一个 Test 文件夹中,那么需要修改如下:
主要是头文件前面要加上对应的路径名称,使用 G++ 编译的时候也需要这么操作。
// main.cpp
#include "Test/Test1.h"
#include "Test/Test2.h"
int main(int argc, char* argv[]) {
PrintHelloWorld();
std::cout<<"a + b = " << MyAdd(10.0, 9.0)<<std::endl;
std::cout<<"(a + b) * a = " << MyMul(10.0, 9.0)<<std::endl;
return 0;
}
编译该文件
g++ ./Test/Test1.cpp ./Test/Test2.cpp main.cpp -lm -o main
执行生成的 HelloWorld.exe 文件,输出 Hello World 的字符串
./main.exe
Hello World
a + b = 19
(a + b) * a = 190
使用 CMakeLists 进行编译
从上面的几个例子中可以看出当我们编译的文件比较多的时候,使用 g++ 指令进行编译的时候,还是比较繁琐的,那么这个时候就需要自己去编写一个 CMakeLists.txt 的文件,来帮助我们进行编译了。
编写 CMakeLists.txt
文件结构如下:
Test/
├── CMakeLists.txt
├── build # build 文件夹
├── main.cpp # 主程序
├── Test1/
│────└── include/
│──────└── Test1.h
│────└── src/
│──────└── Test1.cpp
├── Test2/
│────└── include/
│──────└── Test2.h
│────└── src/
│──────└── Test2.cpp
cmake_minimum_required(VERSION 3.10)
project(MyProject)
# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 打印出变量的值
message("主程序路径: ${CMAKE_CURRENT_SOURCE_DIR}")
# 添加头文件搜索路径(关键步骤!)
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/Test1/include
${CMAKE_CURRENT_SOURCE_DIR}/Test2/include
)
# 生成可执行文件(只需要 main.cpp,头文件会自动被包含)
add_executable(my_program main.cpp
Test1/src/Test1.cpp # 添加实现文件
Test2/src/Test2.cpp)
编译的时候,需要进入到 build 为文件夹中,然后执行 cmake 和 make。
由于 Windows 和 Linux 平台上面的不一致性,因此执行 cmake 和 make 的时候会有一些区别。
注意1
在CMake中,-G参数用于指定生成器,即用于生成特定构建系统的Makefiles或项目文件。不同的构建系统有不同的生成器可以选择,例如Unix Makefiles、Ninja、Visual Studio等。
cmake 的时候需要指定 "MinGW Makefiles"为 makefile类型,否则会提示报错。
注意2
如果在使用 make 的时候提示如下错误:
make : 无法将“make”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。
将 MinGW64 目录 \bin 下的 mingw32-make.exe 复制一份,然后重命名为 make.exe 就可以解决该问题。
cd build
cmake .. -G"MinGW Makefiles"
make
make 文件之后会在 build 文件夹中生成对应的可执行文件。
执行生成的 my_program.exe 文件,输出 Hello World 的字符串
.\my_program.exe
Hello World
a + b = 19
(a + b) * a = 190
生成外部库,并进行调用
文件结构如下:
TestDLL/
├── MyAPP/
│────└── src/
│──────└── main.cpp
│────CMakeLists.txt
├── MyDLL/
│────CMakeLists.txt
├────Test1/
│──────└── include/
│─────────└── Test1.h
│──────└── src/
│─────────└── Test1.cpp
├────Test2/
│──────└── include/
│─────────└── Test2.h
│──────└── src/
│─────────└── Test2.cpp
MyDLL 中的 CMakeLists.txt 的写法
cmake_minimum_required(VERSION 3.10)
project(MyDLL)
# # 设置库类型(STATIC 静态库,SHARED 动态库)
# set(LIB_TYPE STATIC)
# 设置 DLL 导出宏
add_definitions(-DMYDLL_EXPORTS)
# 包含头文件目录
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/Test1/include
${CMAKE_CURRENT_SOURCE_DIR}/Test2/include
)
# 添加库源文件
file(GLOB DLL_SOURCES "Test1/src/*.cpp" "Test2/src/*.cpp")
# 创建输出目录
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/../bin)
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/../lib)
# 设置输出目录,需要在 add_library,否则 make 之后不会将对应的文件复制到这些文件夹中
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/../bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/../bin)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/../lib)
# 创建动态链接库
add_library(MyDLL SHARED ${DLL_SOURCES})
# 安装规则(可选)
install(TARGETS MyDLL
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin)
install(DIRECTORY include/ DESTINATION include)
MyAPP 中的 CMakeLists.txt 的写法
cmake_minimum_required(VERSION 3.10)
project(MyAPP)
# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
# 设置输出目录
set(TARGET_OUTPUT_DIR "${CMAKE_BINARY_DIR}/../bin")
# 创建目标目录(如果不存在)
file(MAKE_DIRECTORY ${TARGET_OUTPUT_DIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${TARGET_OUTPUT_DIR})
# 定义头文件搜索路径列表
set(MY_PROJECT_INCLUDE_PATHS
${CMAKE_CURRENT_SOURCE_DIR}/../MyDLL/Test1/include
${CMAKE_CURRENT_SOURCE_DIR}/../MyDLL/Test2/include
"$ENV{CUSTOM_LIB_PATH}/include"
)
# 查找 DLL 库文件
find_library(MYDLL_LIBRARY
NAMES libMyDLL
PATHS ${CMAKE_SOURCE_DIR}/../MyDLL/bin
NO_DEFAULT_PATH)
if(NOT MYDLL_LIBRARY)
message(FATAL_ERROR "MyDLL library not found")
endif()
# 添加可执行文件
add_executable(myapp src/main.cpp)
# 包含 DLL 头文件目录
target_include_directories(myapp PRIVATE ${MY_PROJECT_INCLUDE_PATHS})
# 链接 DLL
target_link_libraries(myapp PRIVATE ${MYDLL_LIBRARY})
# 更健壮的 DLL 复制命令
# 在 Windows 系统中,当主程序(.exe)依赖动态链接库(DLL)时,必须将 DLL 文件放置在可执行文件能够找到的位置,否则程序将无法运行。
if(WIN32)
# 首先确保源文件存在
if(EXISTS "${CMAKE_SOURCE_DIR}/../MyDLL/bin/libMyDLL.dll")
# 添加复制命令
add_custom_command(TARGET myapp POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
"${CMAKE_SOURCE_DIR}/../MyDLL/bin/libMyDLL.dll"
${TARGET_OUTPUT_DIR}
COMMENT "Copying libMyDLL.dll to output directory"
)
else()
message(WARNING "libMyDLL.dll not found at: ${CMAKE_SOURCE_DIR}/../MyDLL/bin/libMyDLL.dll")
endif()
endif()
编译文件然后进行执行
编译
cmake .. -G"MinGW Makefiles"
make
执行
..\bin\myapp.exe
Hello World
a + b = 19
(a + b) * a = 190
注意
- 在 Windows 系统中,当主程序(.exe)依赖动态链接库(DLL)时,必须将 DLL 文件放置在可执行文件能够找到的位置,否则程序将无法运行。
- 当你编译成功,也生成了对应的 exe 文件,但是在执行的时候却发现在终端中没有任何的输出,那么应该就是对应 dll 文件在执行的时候没有找到导致的。当时也是找这个找了很久的原因。
生成外部库,并进行调用,每一个模块中都有对应的 CMakeLists
文件结构如下:
TestDLL/
├── CMakeLists.txt
├── MyAPP/
│────└── src/
│──────└── main.cpp
│────CMakeLists.txt
├── MyDLL/
│────CMakeLists.txt
│────Module
├────────Test1/
├─────────└── include/
├────────────└── Test1.h
├─────────└── src/
├────────────└── Test1.cpp
├─────────CMakeLists.txt
├───────Test2/
├─────────└── include/
├────────────└── Test2.h
├─────────└── src/
├────────────└── Test2.cpp
├─────────CMakeLists.txt
├───────Test3/
├─────────└── include/
├────────────└── Test3.h
├─────────└── src/
├────────────└── Test3.cpp
├─────────CMakeLists.txt
三个模块中的 CMakeList 写法
Test1
cmake_minimum_required(VERSION 3.10)
project(libTest1)
add_library(libTest1 OBJECT src/Test1.cpp) # 使用 OBJECT 库
# 导出头文件路径
target_include_directories(libTest1 PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
)
Test2
cmake_minimum_required(VERSION 3.10)
project(libTest2)
# 包含头文件目录
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/../Test1/include
)
add_library(libTest2 OBJECT src/Test2.cpp) # 使用 OBJECT 库
# 导出头文件路径
target_include_directories(libTest2 PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
)
Test3
cmake_minimum_required(VERSION 3.10)
project(libTest3)
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/../Test1/include
${CMAKE_CURRENT_SOURCE_DIR}/../Test2/include
)
add_library(libTest3 OBJECT src/Test3.cpp) # 使用 OBJECT 库
# 导出头文件路径
target_include_directories(libTest3 PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
)
编译生成外部动态库的 CMakeLists
cmake_minimum_required(VERSION 3.10)
project(MyDLL)
# 设置 DLL 导出宏
add_definitions(-DMyDLL_EXPORTS)
# 添加所有子模块
# 在这里就会开始去编译各个子模块中的 CMakeLists 文件了
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Module/Test1 ${CMAKE_BINARY_DIR}/Test1)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Module/Test2 ${CMAKE_BINARY_DIR}/Test2)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Module/Test3 ${CMAKE_BINARY_DIR}/Test3)
# 创建输出目录
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/../bin)
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/../lib)
# 设置输出目录
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/../bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/../bin)
# 将 .a 文件也输出到 bin 文件目录下面
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/../bin)
# 合并生成动态库
add_library(MyDLL SHARED
$<TARGET_OBJECTS:libTest1>
$<TARGET_OBJECTS:libTest2>
$<TARGET_OBJECTS:libTest3>
)
# 导出所有头文件路径
target_include_directories(MyDLL PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_SOURCE_DIR}/Test1/include
${CMAKE_SOURCE_DIR}/Test2/include
${CMAKE_SOURCE_DIR}/Test3/include
)
# 安装规则(可选)
install(TARGETS MyDLL
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin)
install(DIRECTORY include/ DESTINATION include)
# Windows符号导出
if(WIN32)
target_compile_definitions(MyDLL PRIVATE MYDLL_EXPORTS)
endif()
APP中的 CMakeLists
cmake_minimum_required(VERSION 3.10)
project(MyAPP)
# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
# 设置输出目录
set(TARGET_OUTPUT_DIR "${CMAKE_BINARY_DIR}/../bin")
# 创建目标目录(如果不存在)
file(MAKE_DIRECTORY ${TARGET_OUTPUT_DIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${TARGET_OUTPUT_DIR})
# 定义头文件搜索路径列表
set(MY_PROJECT_INCLUDE_PATHS
${CMAKE_CURRENT_SOURCE_DIR}/../MyDLL/Module/Test1/include
${CMAKE_CURRENT_SOURCE_DIR}/../MyDLL/Module/Test2/include
${CMAKE_CURRENT_SOURCE_DIR}/../MyDLL/Module/Test3/include
"$ENV{CUSTOM_LIB_PATH}/include"
)
# 查找 DLL 库文件
find_library(MyDLL_LIBRARY
NAMES libMyDLL
PATHS ${CMAKE_CURRENT_SOURCE_DIR}/../MyDLL/bin
NO_DEFAULT_PATH)
if(NOT MyDLL_LIBRARY)
message(FATAL_ERROR "MyDLL library not found")
endif()
# 添加可执行文件
add_executable(myapp src/main.cpp)
# 包含 DLL 头文件目录
target_include_directories(myapp PRIVATE ${MY_PROJECT_INCLUDE_PATHS})
# 链接 DLL
target_link_libraries(myapp PRIVATE ${MyDLL_LIBRARY})
# 更健壮的 DLL 复制命令
# 在 Windows 系统中,当主程序(.exe)依赖动态链接库(DLL)时,必须将 DLL 文件放置在可执行文件能够找到的位置,否则程序将无法运行。
if(WIN32)
# 首先确保源文件存在
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../MyDLL/bin/libMyDLL.dll")
# 添加复制命令
add_custom_command(TARGET myapp POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
"${CMAKE_CURRENT_SOURCE_DIR}/../MyDLL/bin/libMyDLL.dll"
${TARGET_OUTPUT_DIR}
COMMENT "Copying libMyDLL.dll to output directory"
)
else()
message(WARNING "libMyDLL.dll not found at: ${CMAKE_CURRENT_SOURCE_DIR}/../MyDLL/bin/libMyDLL.dll")
endif()
endif()
最外层的 CMAkeLists
cmake_minimum_required(VERSION 3.10)
project(main_project)
# 设置可执行文件输出到 build/
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
# 添加子模块(关键步骤)
add_subdirectory(MyDLL)
add_subdirectory(MyAPP)
这个 CMAkeLists 主要是为了更好的管理整个文件夹中的 CMakeLists 文件,如果只是为了单独编译 MyAPP/src/main.cpp 的话,那么也可以不需要这个 CMAkeLists.txt, 直接在 MyAPP 中新建一个 build 文件夹进行编译即可。