如何在VSCode 中采用CMake编译C++程序

发布于:2025-03-31 ⋅ 阅读:(24) ⋅ 点赞:(0)

如何在VSCode 中采用CMake编译C++程序

配置环境

1、如何解决Windows下无法编译std::thread 库的问题

一个简单的确认方法就是在终端中输入 g++ -v,查看输出中的【Thread model: 】中的参数值是否为【posix】,如果是【Win32】 则无法编译 std::thread 库。

2、如何解决无法识别 Cmake 指令的问题

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.hTest1.cppTest2.hTest2.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 文件夹进行编译即可。