如果你在 Windows 上同时进行 C/C++ 和 Rust 开发,大概率遇到过这样的困境:想用最新的 C++23 特性,却发现 MSVC 支持不全;想编译一些依赖 GCC 的 C 库给 Rust 的build.rs用,却发现环境变量配得一团糟;好不容易找到一个 GCC,结果 CMake 又找不到它。这种工具链的割裂感,是 Windows 平台 C/C++/Rust 混合开发中最消耗心力的“暗伤”。
本文要解决的,正是这个具体而微的痛点:如何在 Windows 上,配置一套统一、现代、且能与 Rust 工具链(Cargo)无缝协作的 GCC 环境。我们的目标不是简单地安装一个 GCC,而是构建一个以 GCC 为核心、CMake 为构建系统、并能被 Rust 原生识别和调用的完整工具链。这能让你在 Windows 上获得接近 Linux/macOS 的开发体验,尤其是在处理跨语言项目或依赖原生 C/C++ 库的 Rust 项目时。
经过多次尝试和对比,目前最稳定、最前沿的解决方案是:WinLibs 构建的 GCC 15 + CMake 4的组合。这个组合的优势在于:
- GCC 15 的前沿性:提供了对 C++23、C2x 标准最完整的实验性支持,让你能提前体验下一代语言特性。
- WinLibs 的纯净与完整:它提供了包含运行时库、头文件、GDB 等在内的完整 MinGW-w64 发行版,开箱即用,避免了手动拼凑工具链的麻烦。
- CMake 4 的现代构建管理:新版 CMake 对 Ninja 生成器、预设(Presets)等现代工作流支持更好,能更智能地定位各种工具链。
- 与 Rust 的天然亲和:Rust 的
cc和cmakecrate 能自动发现并使用系统 PATH 中的 GCC 和 CMake,实现 C/C++ 依赖的自动编译。
接下来,我将从为什么选这个组合、详细配置步骤、与 Rust 项目的集成实战,到常见坑点排查,为你完整呈现这套工作流的搭建过程。无论你是想统一团队构建环境,还是单纯厌倦了 MSVC 与 MinGW 的切换之苦,这篇文章都能提供一条清晰的路径。
1. 为什么是 WinLibs 的 GCC 15 和 CMake 4?
在 Windows 上,C/C++ 编译器主要有 MSVC 和 MinGW-w64(GCC)两大阵营。对于需要与 Rust 紧密协作的场景,GCC 往往是更优选择。
MSVC 的局限:
- 标准支持滞后:虽然稳定,但对最新 C++ 标准的跟进速度通常慢于 GCC/Clang。
- ABI 兼容性问题:MSVC 编译的库与 GCC 编译的库(包括 Rust 默认使用的
rustc链接的 MinGW 目标)ABI 不兼容,直接混用会导致链接错误或运行时崩溃。 - 环境依赖重:需要安装完整的 Visual Studio 或庞大的 Build Tools,不够轻量。
MinGW-w64 GCC 的优势:
- 标准支持快:GCC 是标准推进的积极实践者,能让你更快用上新特性。
- 与 Rust 工具链同源:Rust 的
x86_64-pc-windows-gnu工具链本身就是基于 MinGW-w64 的,使用 GCC 可以确保 C 库的 ABI 完全一致。 - 轻量且跨平台:工具链本身相对独立,配置好环境变量即可,更符合 Unix-like 的开发习惯。
为什么选择 WinLibs?网络上 MinGW-w64 发行版众多(如 MSYS2, MinGW-w64 installer)。WinLibs 的独特优势在于:
- 独立便携:提供一个压缩包,解压即用,无需通过包管理器安装,不污染系统。
- 版本丰富且新:它及时提供基于最新 GCC 主线版本的构建,比如 GCC 15。
- 包含完整工具链:除了 GCC,还包含 GDB、make、binutils 等,甚至可选 Clang/LLVM,一站式配齐。
为什么需要 CMake 4?CMake 是现代 C/C++ 项目的构建系统事实标准。Rust 的cmakecrate 在编译 C/C++ 依赖时,会调用系统的 CMake。
- 更好的工具链探测:CMake 4 对非 MSVC 工具链的探测逻辑更健壮。
- 预设(Presets)功能:可以声明式地定义构建配置,简化命令行操作,这对自动化脚本和 CI/CD 友好。
- 对 Ninja 的优先支持:Ninja 比 GNU Make 构建速度更快,CMake 4 对其支持更完善。
这个组合的核心价值是:用一个最新、最全、最兼容的 GCC 工具链,配合一个现代化的构建系统,为 Rust 的 C/C++ 交互铺平道路,最终在 Windows 上实现高效、无痛的混合语言开发。
2. 环境准备与工具下载
在开始之前,请确保你的系统是 Windows 10 或 Windows 11,并拥有基本的命令行操作权限。
2.1 下载 WinLibs GCC 15
- 访问 WinLibs 官方发布页面:
https://winlibs.com/。 - 找到 “UCRT runtime” 版本的 GCC。UCRT(Universal C Runtime)是现代 Windows 10+ 的系统运行时,比旧的 MSVCRT 更推荐。选择包含GCC 15.x.x和Mingw-w64 11.0的版本。例如:
gcc-15.2.0-mingw-w64ucrt-12.0.0-r1.7z。 - 下载
.7z压缩包格式。
2.2 下载 CMake 4
- 访问 CMake 官网下载页面:
https://cmake.org/download/。 - 选择最新稳定版(如 4.x.x)的 Windows
x86_64安装包(.msi)或压缩包(.zip)。为了环境纯净,本文推荐使用.zip压缩包(cmake-4.x.x-windows-x86_64.zip)。
2.3 确保 Rust 工具链已安装
你需要一个能正常工作的 Rust 环境。如果你还没有安装,请前往https://rustup.rs/安装rustup。安装时,对于 Windows 平台,务必选择x86_64-pc-windows-gnu作为默认工具链,这将确保 Rust 编译器使用 MinGW-w64 作为后端,与我们将配置的 GCC 完美兼容。
# 安装时通过 rustup 选择 rustup default stable-x86_64-pc-windows-gnu # 或者安装后切换 rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu3. 详细配置步骤:解压与系统集成
假设你将工具都放在D:\DevTools目录下。请根据你的实际情况调整路径。
3.1 安装 WinLibs GCC
- 在
D:\DevTools下新建一个文件夹,例如mingw64。 - 使用 7-Zip 等工具,将下载的 WinLibs
.7z压缩包解压到这个mingw64文件夹中。解压后,目录结构应类似于:D:\DevTools\mingw64\ ├── bin\ # 包含 gcc.exe, g++.exe, gdb.exe, mingw32-make.exe ├── include\ # 系统头文件 ├── lib\ # 系统库文件 └── x86_64-w64-mingw32\ # 目标特定文件 - 将
D:\DevTools\mingw64\bin添加到系统的PATH环境变量中。- 右键点击“此电脑” -> “属性” -> “高级系统设置” -> “环境变量”。
- 在“系统变量”或“用户变量”中找到
Path,点击“编辑”。 - 点击“新建”,添加
D:\DevTools\mingw64\bin。 - 重要:确保这个路径在包含其他编译器(如 MSVC)的路径之前,这样命令行会优先使用我们的 GCC。
3.2 安装 CMake
- 在
D:\DevTools下新建一个文件夹,例如cmake。 - 将下载的 CMake
.zip压缩包解压到cmake文件夹中。目录结构应类似于:D:\DevTools\cmake\ ├── bin\ # 包含 cmake.exe, ctest.exe, cpack.exe └── ... - 同样,将
D:\DevTools\cmake\bin添加到系统的PATH环境变量中。
3.3 验证安装
打开一个新的命令提示符(CMD)或PowerShell窗口(必须新开,以使 PATH 生效),执行以下命令:
# 验证 GCC 版本 gcc --version # 应输出类似:gcc (GCC) 15.2.0 ... g++ --version # 验证 CMake 版本 cmake --version # 应输出类似:cmake version 4.x.x # 验证 GNU Make(WinLibs 提供的是 mingw32-make) mingw32-make --version # 应输出 GNU Make 版本信息 # 验证 Rust 工具链 rustc --version cargo --version # 并确认目标平台 rustup show # 默认工具链应为 stable-x86_64-pc-windows-gnu如果所有命令都能正确输出版本信息,恭喜你,基础工具链配置成功。
4. 核心配置:让 CMake 识别我们的 GCC
仅仅把 GCC 加入 PATH 还不够。CMake 在生成构建文件时,需要明确知道使用哪个编译器。我们需要告诉 CMake 我们的 GCC 位置,特别是当系统存在多个编译器时。
4.1 方法一:通过环境变量指定(推荐)
设置一个名为CMAKE_C_COMPILER和CMAKE_CXX_COMPILER的环境变量,直接指向我们的 GCC。
- 环境变量名:
CMAKE_C_COMPILER - 值:
D:\DevTools\mingw64\bin\gcc.exe - 环境变量名:
CMAKE_CXX_COMPILER - 值:
D:\DevTools\mingw64\bin\g++.exe
你可以在系统环境变量中设置它们,这样所有 CMake 项目都会自动使用这个编译器。
4.2 方法二:在 CMake 命令行中指定
在运行cmake命令时,通过-D选项传递。
# 在项目构建目录中 cmake -G "MinGW Makefiles" ^ -D CMAKE_C_COMPILER="D:/DevTools/mingw64/bin/gcc.exe" ^ -D CMAKE_CXX_COMPILER="D:/DevTools/mingw64/bin/g++.exe" ^ -D CMAKE_MAKE_PROGRAM="D:/DevTools/mingw64/bin/mingw32-make.exe" ^ [path/to/your/source]-G "MinGW Makefiles"指定生成器为 MinGW Makefiles,这是配合 MinGW-w64 GCC 使用的。
4.3 方法三:使用 CMake Presets(CMake 4+ 推荐)
这是最现代、最清晰的方式。在你的项目根目录创建一个CMakePresets.json文件。
{ "version": 8, "configurePresets": [ { "name": "mingw64-default", "displayName": "MinGW-w64 GCC Default Config", "description": "使用 WinLibs GCC 15 进行配置", "generator": "Ninja", // 或 "MinGW Makefiles" "cacheVariables": { "CMAKE_C_COMPILER": "D:/DevTools/mingw64/bin/gcc.exe", "CMAKE_CXX_COMPILER": "D:/DevTools/mingw64/bin/g++.exe", "CMAKE_MAKE_PROGRAM": "D:/DevTools/mingw64/bin/mingw32-make.exe" // 如果 generator 是 MinGW Makefiles }, "environment": { "PATH": "D:/DevTools/mingw64/bin;%PATH%" }, "binaryDir": "${sourceDir}/build/${presetName}" } ] }配置完成后,只需运行:
cmake --preset=mingw64-defaultCMake 会自动应用所有配置。使用 Ninja 生成器需要额外下载ninja.exe并放入 PATH,它比 make 更快。
5. 实战:在 Rust 项目中调用 C/C++ 库
这是检验我们配置成功与否的关键。我们将创建一个 Rust 项目,其中通过build.rs脚本,使用我们配置的 GCC 和 CMake 来编译一个简单的 C++ 库。
5.1 创建项目与 C++ 库
- 创建一个新的 Rust 库项目:
cargo new rust_calls_cpp --lib cd rust_calls_cpp - 在项目根目录创建
cpp_src文件夹,并添加一个简单的 C++ 类:文件:cpp_src/mylib.cpp
文件:// 使用一些简单的 C++ 特性 #include <string> #include <vector> #include <iostream> extern "C" { // 使用 C 链接规范,便于 Rust 调用 int add_numbers(int a, int b) { return a + b; } const char* greet(const char* name) { // 注意:这里返回的指针在 Rust 中需要妥善管理生命周期 // 实际项目中应使用更安全的方式(如返回结构体、由 Rust 分配缓冲区等) static std::string greeting = "Hello, "; greeting = "Hello, "; greeting += name; greeting += "!"; return greeting.c_str(); } // 演示 C++ 标准库的使用(GCC 15 能良好支持) void print_vector() { std::vector<int> vec = {1, 2, 3, 4, 5}; for (auto& v : vec) { // C++11 范围 for 循环 std::cout << v << " "; } std::cout << std::endl; } }cpp_src/mylib.h#ifndef MYLIB_H #define MYLIB_H #ifdef __cplusplus extern "C" { #endif int add_numbers(int a, int b); const char* greet(const char* name); void print_vector(); #ifdef __cplusplus } #endif #endif // MYLIB_H
5.2 编写build.rs脚本
build.rs是 Cargo 在编译 Rust 代码前执行的构建脚本。我们将在这里调用 CMake。文件:build.rs(放在项目根目录,与Cargo.toml同级)
use std::env; use std::path::PathBuf; use cmake::Config; fn main() { // 告诉 Cargo 如果 cpp_src 下的文件变化了,要重新运行 build.rs println!("cargo:rerun-if-changed=cpp_src/"); // 使用 `cmake` crate 来构建 C++ 库 let dst = Config::new("cpp_src") .profile("Release") // 或 "Debug" .define("CMAKE_BUILD_TYPE", "Release") // 非常重要!显式指定我们的工具链。 // 这里假设你已经将 CMAKE_C_COMPILER 等设置为环境变量。 // 如果没有,可以在这里通过 .cflag、.cxxflag 传递,但更推荐环境变量或 Presets。 .build(); // 告诉 Cargo 链接生成的库。 // 库名是 `mylib`,在 Windows 上会生成 `libmylib.a` (静态库) 或 `mylib.dll` (动态库)。 // 这里我们链接静态库。 println!("cargo:rustc-link-search=native={}", dst.join("lib").display()); println!("cargo:rustc-link-lib=static=mylib"); // 如果链接动态库,则需要将 dll 文件复制到输出目录。 // 这里以静态库为例。 // 对于动态库,还需要处理运行时依赖。 }5.3 配置Cargo.toml
添加cmake和cc作为构建依赖。cccrate 更轻量,适合简单 C 代码;cmakecrate 适合已有 CMakeLists.txt 的复杂 C/C++ 项目。文件:Cargo.toml
[package] name = "rust_calls_cpp" version = "0.1.0" edition = "2021" # 构建依赖 [build-dependencies] cmake = "0.1" # 用于在 build.rs 中驱动 CMake # cc = "1.0" // 如果不用 CMake,可以用 cc crate 直接编译单个 C 文件 [dependencies] libc = "0.2" # 用于 C 类型转换5.4 创建CMakeLists.txt
在cpp_src目录下创建CMakeLists.txt,指导 CMake 如何构建我们的库。文件:cpp_src/CMakeLists.txt
cmake_minimum_required(VERSION 3.15) project(mylib LANGUAGES CXX) # 设置 C++ 标准 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) # 不使用编译器特定扩展 # 创建库目标 add_library(mylib STATIC mylib.cpp) # 设置包含目录(如果需要公开头文件) target_include_directories(mylib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) # 安装指令(可选,用于系统级安装) # install(TARGETS mylib ARCHIVE DESTINATION lib) # install(FILES mylib.h DESTINATION include)5.5 编写 Rust 代码调用 C++ 库
文件:src/lib.rs
use std::ffi::CStr; use std::os::raw::c_char; // 声明外部 C 函数,对应 mylib.h 中的内容 extern "C" { fn add_numbers(a: i32, b: i32) -> i32; fn greet(name: *const c_char) -> *const c_char; fn print_vector(); } pub fn rust_add(a: i32, b: i32) -> i32 { unsafe { add_numbers(a, b) } } pub fn rust_greet(name: &str) -> String { let c_name = std::ffi::CString::new(name).expect("CString::new failed"); let c_greeting: *const c_char = unsafe { greet(c_name.as_ptr()) }; // 将 C 字符串转换为 Rust 字符串 let greeting_cstr = unsafe { CStr::from_ptr(c_greeting) }; greeting_cstr.to_string_lossy().into_owned() } pub fn rust_print_vector() { unsafe { print_vector() }; } #[cfg(test)] mod tests { use super::*; #[test] fn test_addition() { assert_eq!(rust_add(2, 3), 5); assert_eq!(rust_add(-1, 1), 0); } #[test] fn test_greet() { let greeting = rust_greet("CSDN"); assert!(greeting.contains("CSDN")); println!("Greeting: {}", greeting); } #[test] fn test_vector() { // 这个函数打印到标准输出,我们主要确认它能被调用而不崩溃 rust_print_vector(); } }5.6 构建并运行
在项目根目录打开配置好 PATH 的命令行,执行:
cargo buildCargo 会:
- 运行
build.rs。 build.rs中的cmakecrate 会调用系统的 CMake(即我们配置的 CMake 4)。- CMake 会根据
CMakeLists.txt和系统环境变量(CMAKE_CXX_COMPILER)找到我们的 GCC 15,编译mylib.cpp生成静态库libmylib.a。 - Cargo 链接这个静态库,并编译 Rust 代码。
构建成功后,运行测试:
cargo test你应该能看到测试通过,并且test_vector测试会在控制台输出1 2 3 4 5。
6. 运行结果与效果验证
如果一切顺利,你将看到类似以下输出:
Compiling rust_calls_cpp v0.1.0 (D:\projects\rust_calls_cpp) Finished `dev` profile [unoptimized + debuginfo] target(s) in 5.20s Running target\debug\deps\rust_calls_cpp-xxxxxxx.exe running 3 tests test tests::test_addition ... ok test tests::test_vector ... ok test tests::test_greet ... ok test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out这证明了:
- GCC 15 被成功调用:它编译了使用了 C++11/17 特性的 C++ 代码。
- CMake 4 工作正常:它被
cmakecrate 调用,并正确生成了构建系统。 - Rust 与 C++ 链接成功:Rust 代码能够无缝调用 GCC 编译生成的静态库函数。
- 整个工具链协同工作:从 Rust 的
cargo命令开始,到 GCC 编译 C++,再到最终链接成一个可执行测试文件,整个流程是自动化的。
你可以进一步验证 GCC 的版本和 C++ 标准支持:
# 进入 CMake 构建目录(build.rs 生成的临时目录通常在 `target/build/` 下) # 查看 CMake 缓存,确认编译器路径 find . -name CMakeCache.txt -exec grep CMAKE_CXX_COMPILER {} \;7. 常见问题与排查思路
在配置过程中,你可能会遇到以下问题:
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
gcc --version不生效或报错 | 1. PATH 环境变量未生效。 2. 路径添加错误。 3. 与其他软件(如 Git Bash 自带的 MinGW)冲突。 | 1. 新开命令行窗口。 2. echo %PATH%检查路径。3. where gcc查看哪个 gcc 被优先找到。 | 1. 重启终端或电脑。 2. 修正 PATH。 3. 调整 PATH 顺序,确保我们的 mingw64\bin在最前面。 |
| CMake 找不到编译器或使用 MSVC | 1. 未设置CMAKE_CXX_COMPILER等变量。2. 未指定 -G "MinGW Makefiles"。3. 缓存(CMakeCache.txt)中记录了旧的编译器路径。 | 1. 检查环境变量或命令行参数。 2. 运行 cmake -G查看可用生成器。3. 删除 build目录重新生成。 | 1. 使用“方法一”设置永久环境变量。 2. 显式指定生成器和编译器路径。 3. 清理构建目录。 |
| Cargo build 失败,提示链接错误 (LNKxxxx) | 1. Rust 工具链是msvc而非gnu。2. C++ 库与 Rust 链接器不兼容(Debug/Release 混用)。 3. C 函数声明 ( extern "C") 缺失或错误。 | 1.rustup show确认工具链。2. 检查 build.rs中的 profile 和 CMake 的CMAKE_BUILD_TYPE。3. 检查 C++ 头文件中的 extern "C"和 Rust 中的extern "C"声明。 | 1. 切换 Rust 工具链:rustup default stable-gnu。2. 统一构建配置(都使用 Release 或 Debug)。 3. 确保 C 接口声明正确,名称修饰被禁用。 |
undefined reference to ‘xxx’ | 1. 链接库顺序问题。 2. 库文件未找到。 3. C++ 函数未以 extern "C"导出。 | 1. 检查cargo:rustc-link-lib顺序(依赖库在后)。2. 确认 cargo:rustc-link-search路径正确。3. 使用 nm或objdump工具查看库中的符号。 | 1. 调整链接顺序。 2. 打印 dst变量,确认库文件生成位置。3. 确保 C++ 实现文件中的函数也包裹在 extern "C"中。 |
CMake 报错关于std::cout或 C++ 标准库 | GCC 安装不完整或运行时库缺失。 | 检查mingw64\bin下是否有libstdc++-6.dll等文件。 | 确保下载的是完整的 WinLibs UCRT 版本,并解压了全部文件。 |
| 编译速度慢 | 1. 使用了MinGW Makefiles生成器而非Ninja。2. 未启用并行编译。 | 检查 CMake 生成器。 | 1. 安装 Ninja 并将其加入 PATH,在 CMake 中使用-G "Ninja"。2. 对于 Make,使用 mingw32-make -jN(N 为 CPU 核心数)。 |
8. 最佳实践与工程建议
将这套配置用于实际项目时,遵循以下建议可以避免很多麻烦:
- 工具链版本固化:对于团队项目,将特定版本的 WinLibs GCC 压缩包和 CMake 压缩包放入版本控制(如 Git LFS)或内部文件服务器。在项目的
README.md或scripts/目录下提供一键配置脚本,确保所有开发者环境一致。 - 使用 CMake Presets:强烈推荐使用
CMakePresets.json。它可以将编译器路径、生成器、构建类型、缓存变量等定义在一个文件中,与项目代码一同管理。cmake --preset命令让构建指令变得极其简洁。 - 在
build.rs中处理路径:不要将绝对路径硬编码在build.rs中。可以通过环境变量(如MINGW64_PATH)来传递,或者在build.rs中实现逻辑来查找工具链。let mingw_path = env::var("MINGW64_PATH").unwrap_or_else(|_| r"D:\DevTools\mingw64".to_string()); let c_compiler = format!("{}\\bin\\gcc.exe", mingw_path); // ... 在 Config 中通过 .define 传递 - 区分开发与生产环境:在
build.rs中,可以根据profile决定构建类型(Debug/Release)。Debug 版本便于调试,Release 版本性能更好。let profile = env::var("PROFILE").unwrap(); let mut config = Config::new("cpp_src"); if profile == "release" { config.define("CMAKE_BUILD_TYPE", "Release"); } else { config.define("CMAKE_BUILD_TYPE", "Debug"); } - 处理动态库(DLL):如果 C++ 库编译为动态库(
.dll),除了链接,还需要在运行时能找到它。可以在build.rs中将.dll文件复制到 Cargo 的输出目录(OUT_DIR或target/debug)。// 在 .build() 之后 let dll_file = dst.join("bin").join("mylib.dll"); // 假设 DLL 在 bin 目录 if dll_file.exists() { let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); // 复制到更合适的目录,例如 target/debug let target_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) .join("target") .join(&profile); std::fs::copy(&dll_file, target_dir.join("mylib.dll")).unwrap(); } - IDE 集成:在 VSCode 中,安装 “CMake Tools” 扩展。它可以自动读取
CMakePresets.json,让你在 IDE 内轻松切换和运行配置。对于 CLion 等 JetBrains IDE,它们对 CMake 的支持原生且强大,直接打开包含CMakeLists.txt的目录即可。
通过以上步骤和最佳实践,你就在 Windows 上成功搭建了一套以 GCC 15 和 CMake 4 为核心的现代化 C/C++/Rust 混合开发环境。这套环境不仅解决了工具链的统一问题,还通过 CMake Presets 和 Rust 的build.rs实现了构建流程的自动化与可重复性,显著提升了跨语言项目的开发效率和体验。下次当你需要在 Rust 项目中集成一个复杂的 C++ 库时,这套配置将成为你可靠的基石。