资讯动态

Windows下用vcpkg+MinGW构建CMake项目完整指南

发布时间:2026/10/20 5:12:24 来源:尧图企业网站定制
从“MSVC 一把梭”到“跨平台自由”中间只差一个靠谱的构建方案。这段时间我窝在 Windows 环境里调试一个需要跨平台运行的 CMake 项目终于把 vcpkg MinGW 这条路线完整跑通。整个过程远没有想象中顺畅踩了不少坑triplet 配错导致库白装、CMake 检测编译器失败、链接报错一堆 undefined reference、运行时又缺 DLL。把这些经历整理出来给同样打算在 Windows 下用 vcpkg MinGW 构建 CMake 项目的人一份能直接照着做的方案。1. 为什么要折腾 vcpkg MinGW 这套组合1.1 MinGW 与 MSVC不只是换了个编译器那么简单很多 Windows 开发者习惯了 Visual Studio MSVC 的组合CMake 项目一创建默认生成的就是 VS 工程文件点一下生成依赖库用 vcpkg 或 NuGet 装一下一切顺理成章。但当你需要把项目部署到没有 Visual Studio 的机器或者想让同一套代码在 Windows、Linux、macOS 上共用一套 GCC 工具链时MinGW 就成了绕不开的选择。MinGW-w64 本质上是 GCC 编译器在 Windows 平台的移植实现它直接生成 Windows 可执行文件但编译出来的二进制与 MSVC 编译的二进制并不通用。这背后是 ABI应用二进制接口的问题两者对 C 类的内存布局、名字修饰规则、异常处理机制、标准库实现都有差异。用生活化的类比来说MSVC 和 MinGW 就像同一条生产线的两套模具看起来都能产出零件但彼此生产的零件尺寸公差对不上强行组装就会出问题。所以在一个 CMake 项目里你不能混用 MSVC 编译的库和 MinGW 编译的库。这也是后面很多报错的根源从网上直接下载的预编译库十有八九是按 MSVC 编译的拿给 MinGW 链接大概率要翻车。1.2 vcpkg 在 Windows 下的真实角色vcpkg 最初是微软官方维护的 C 包管理器后来变成了开源项目。它做的事情很直白把第三方 C/C 库的源码下载到本地按照你指定的工具链和配置编译成二进制然后注册到项目里方便 CMake 查找和链接。在 Windows MinGW 这个组合里vcpkg 的价值被放大了。本来你要手动去 GitHub 上拉代码、按文档配置编译参数、再自己维护版本更新现在只需要一条命令vcpkg install fmt --triplet x64-mingw-dynamic它会自动完成下载、解压、配置、编译、安装的全流程并把编译好的库文件放到 vcpkg 的安装目录里。后续项目构建时CMake 通过 vcpkg 提供的 toolchain 文件就能自动找到这些依赖。另外 vcpkg 有两种工作模式。早期大家用的是 classic 模式直接在根目录全局安装库现在我更推荐 manifest 模式在每个项目里放一个 vcpkg.json 文件声明项目依赖什么库、什么版本。这种方式的最大好处是可复现换一台机器、换一个同事克隆代码后第一次构建就能自动拉取依赖并编译不会出现“我这边能跑你那边跑不了”的尴尬。1.3 这套方案的适用场景与取舍先明确一个态度并不是所有场景都适合 vcpkg MinGW。我的建议是如果你的团队深度依赖 Visual Studio 的调试体验、用到 Windows 专属 SDK 的高级特性比如 ATL/MFC、自定义 MSBuild Task那么继续用 MSVC 工具链更稳妥。MinGW 的价值主要体现在这些场景场景推荐程度原因跨平台项目代码需要跑 Linux/macOS/Windows非常推荐统一用 GCC 工具链减少条件编译差异CI/CD 流水线用非 Windows 节点编译推荐MinGW 不依赖 VS 安装镜像体积小希望在 Windows 上体验 GNU 工具链生态推荐与 Linux 上开发习惯完全一致重度使用 VS 调试、性能分析器不推荐MinGW 不支持 PDB 调试格式调试体验打折扣项目大量依赖 MSVC 预编译商业库不推荐商业库大多只提供 MSVC 版二进制2. 环境准备与核心概念triplet 和 toolchain2.1 三件套安装MinGW-w64、CMake、vcpkg先说工具链。MinGW-w64 的发行包建议选择带线程模型为 win32、异常处理模型为 SEH 的版本64 位环境下这组合最稳定。安装时记住一个原则安装路径不能有空格和中文否则 CMake 检测编译器时大概率会出诡异的问题。我习惯把工具链放在C:\tools\mingw64这种地方并把C:\tools\mingw64\bin加入系统 PATH。接着是 CMake。建议装 3.20 以上的版本太老的版本对 vcpkg toolchain 和 MinGW 生成器的支持不完整。Windows 上安装 CMake 时会问是否加入 PATH务必勾选这样命令行里才能直接用 cmake 命令。最后是 vcpkg。它的安装方式很简单git clone https://github.com/microsoft/vcpkg.git cd vcpkg bootstrap-vcpkg.batbootstrap 过程会编译出 vcpkg.exe。之后建议把 vcpkg 目录路径设置到环境变量VCPKG_ROOT中CMake 的 toolchain 文件路径可以基于这个变量自动拼接省去手写绝对路径的麻烦。2.2 理解 triplet为什么不能用 x64-windows如果你是 vcpkg 新手最容易踩的坑就在 triplet 这个概念上。triplet 是 vcpkg 用来描述“依赖库应该以什么工具链、什么配置进行编译”的一组字符串。在 Windows 上默认的 triplet 是x64-windows对应的是 MSVC 工具链。如果你直接用它安装库得到的库就是 MSVC ABI 版本后续用 MinGW 去链接会出现一堆莫名其妙的链接错误甚至直接报找不到符号。MinGW 对应的 triplet 有两个x64-mingw-dynamic和x64-mingw-static。前者生成动态链接库DLL后者生成静态链接库LIB/静态 .a。这两个的选择会影响运行时的部署方式后面会详细讲。这里要记住一条铁律整个项目里所有依赖库的 triplet 必须保持一致并且必须和 CMake 配置时指定的VCPKG_TARGET_TRIPLET一致。混用不同 triplet 的库是自找麻烦。2.3 toolchain 文件vcpkg 与 CMake 的桥梁CMake 能感知到 vcpkg 安装的依赖靠的是一个 toolchain 文件。vcpkg 仓库自带了这个文件路径是scripts/buildsystems/vcpkg.cmake。在配置项目时通过CMAKE_TOOLCHAIN_FILE参数指定即可。很多人混淆了vcpkg integrate install和 toolchain 文件的作用。vcpkg integrate install主要影响 Visual Studio 的 MSBuild 工程让 VS 能识别 vcpkg 的安装目录。但 CMake 项目并不会自动读取这个集成信息必须显式加 toolchain 参数。另外如果你在使用某个 IDE 配置 CMake 项目通常也要手动把 toolchain 文件路径填进去。使用命令行配置时还需要注意生成器的选择。MinGW 工具链不能使用 Visual Studio 生成器常见的选择是MinGW Makefiles或Ninja。用MinGW Makefiles时CMake 会自动在 PATH 中查找 MinGW 的编译器如果用Ninja建议显式指定 C/C 编译器避免 CMake 误选 MSVC。3. 实际构建一个带第三方依赖的 CMake 项目3.1 项目结构与核心源码我准备了一个十分典型的示例用 fmt 做格式化输出用 spdlog 打日志。这两个库一个是纯头文件友好的格式化库一个是成熟且依赖链不复杂的日志库很适合用来验证整条链路是否通畅。项目的目录结构如下mingw_vcpkg_demo/ ├── CMakeLists.txt ├── vcpkg.json ├── src/ │ └── main.cpp └── build/main.cpp 的代码非常简单#include fmt/format.h #include spdlog/spdlog.h int main() { spdlog::info(Hello from spdlog); auto s fmt::format(The answer is {}, 42); spdlog::info(fmt says: {}, s); return 0; }3.2 CMakeLists.txt 的编写细节CMakeLists.txt 是最需要理解的一份文件。在 vcpkg MinGW 环境中第三方库的查找方式与传统的find_package有微妙的差别。vcpkg 安装的库大多提供了 CMake config 文件因此推荐使用 CONFIG 模式查找cmake_minimum_required(VERSION 3.15) project(demo LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(fmt CONFIG REQUIRED) find_package(spdlog CONFIG REQUIRED) add_executable(demo src/main.cpp) target_link_libraries(demo PRIVATE fmt::fmt spdlog::spdlog)一个小细节find_package(fmt CONFIG REQUIRED)中的CONFIG关键字表示只使用 CMake 的配置文件搜索模式而不是 module 模式。vcpkg 安装的库目录下一般会生成fmt-config.cmake或fmtConfig.cmake这类文件CONFIG 模式能够精确地找到它们。如果不加 CONFIGCMake 会先查找 Find 模块在这个场景下往往会扑空。链接时fmt::fmt和spdlog::spdlog是这些库通过 config 文件导出的 CMake target 名字不同库的导出名不一定和库名完全一致不确定时可以去对应的配置文件里查看。3.3 vcpkg.json用 manifest 模式管理依赖在项目根目录创建 vcpkg.json{ name: mingw-vcpkg-demo, version-string: 1.0.0, dependencies: [ fmt, spdlog ] }当 CMake 通过 vcpkg toolchain 配置项目时会检测到这个 vcpkg.json自动触发依赖安装过程。也就是说你甚至不需要手动执行vcpkg install命令cmake 配置阶段就会把依赖装好。这在团队协作中非常友好因为依赖版本被记录在项目里而不是只存在于某一个人的本地环境。如果要锁定版本可以在 vcpkg.json 中加一个builtin-baseline字段或者用vcpkg manifest相关的命令生成锁定文件。这块细节不少初期先保证依赖能自动安装即可版本锁定以后可以再完善。3.4 构建命令cmd 和 PowerShell 的写法都要会完整构建过程分两阶段配置和编译。在项目根目录执行以下命令cmd 版本cmake -B build -S ^ -G MinGW Makefiles ^ -DCMAKE_BUILD_TYPERelease ^ -DCMAKE_TOOLCHAIN_FILE%VCPKG_ROOT%\scripts\buildsystems\vcpkg.cmake ^ -DVCPKG_TARGET_TRIPLETx64-mingw-dynamic cmake --build build -j8PowerShell 用户注意环境变量引用方式不同换行符也不同cmake -B build -S . -G MinGW Makefiles -DCMAKE_BUILD_TYPERelease -DCMAKE_TOOLCHAIN_FILE$env:VCPKG_ROOT\scripts\buildsystems\vcpkg.cmake -DVCPKG_TARGET_TRIPLETx64-mingw-dynamic解释一下几个参数的意义-B build指定 CMake 构建目录为 build避免源码目录内产生混乱的中间文件。-G MinGW Makefiles让 CMake 生成 MinGW 可用的 makefile而不是 VS 工程。-DCMAKE_BUILD_TYPERelease在单配置生成器MinGW Makefiles 和 Ninja 都是单配置下必须显式指定构建类型。这件事特别容易被忽视不写的话默认是空构建类型很可能不会启用优化还会少一些编译宏。-DCMAKE_TOOLCHAIN_FILE指定 vcpkg 的 toolchain 文件这一步是整个集成的关键。-DVCPKG_TARGET_TRIPLET指定当前项目使用的依赖库 ABI 目标。这里的值必须与依赖库安装时的 triplet 一致。配置执行完后CMake 会在终端输出 vcpkg 启动依赖安装的信息。如果一切顺利你会看到 fmt 和 spdlog 被自动拉取、编译最后显示Found fmt、Found spdlog。编译阶段输出多了会刷屏但最应该关注的是最后链接是否成功。正常情况下会生成 build/src/demo.exe。3.5 静态 vs 动态在跑起来之前先做决定前面提到过x64-mingw-dynamic和x64-mingw-static两种 triplet。具体选择可以从两个角度考虑。如果你选动态库那么编译产物会依赖多个 DLL。运行 demo.exe 之前必须把 vcpkg 安装目录下的 DLL 目录加入 PATH或者把相关的 DLL 拷贝到 exe 同目录。这些 DLL 位于 build 目录下 vcpkg 安装的子目录中一般是build/vcpkg_installed/x64-mingw-dynamic/bin。直接启动 demo.exe 而不配置 PATH大概率会弹出“找不到 fmt.dll”之类的系统错误框。如果你选静态库那么所有依赖都会被链接进 exe运行时无需额外 DLL。分发更省心缺点是 exe 体积会变大而且有些库在静态编译时还需要额外链接系统底层库比如-lws2_32、-lwinmm。vcpkg 的 port 描述文件通常会处理好这些链接关系但也不排除某些库需要你在 CMakeLists 里手动补充target_link_libraries的系统库。我的建议是开发阶段用x64-mingw-dynamic省事、构建快发布阶段用x64-mingw-static分发干净。只要记住切换 triplet 后必须删掉 build 目录重新配置否则很容易因为缓存残留导致库文件新旧混杂。4. 高频报错与排查方法4.1 find_package 找不到包典型报错CMake Error at CMakeLists.txt:8 (find_package): Could not find a package configuration file provided by fmt这个问题的头号原因是 toolchain 文件没有实际生效。怎么排查先去 build 目录下的 CMakeCache.txt 里搜下面这两个变量CMAKE_TOOLCHAIN_FILE:FILEPATH... VCPKG_TARGET_TRIPLET:STRING...如果 CMAKE_TOOLCHAIN_FILE 为空或路径不对就说明 CMake 配置时根本没加载 vcpkg 的集成。这时检查命令里的-DCMAKE_TOOLCHAIN_FILE参数是否写错或者%VCPKG_ROOT%环境变量是否已设置。另一个常见原因是用 IDE 配置 CMake 项目时IDE 自己维护了一套 CMake 配置缓存命令行的参数不会自动带进去。此时需要在 IDE 的 CMake 配置选项里手动填入 toolchain 路径和 triplet。这种模式下的包名也要确认vcpkg 安装的库名与 find_package 查找的包名不一定相同。比如zlib包对应的 CMake 包名是ZLIBlibpng对应PNG。不确定时去安装目录里看看生成了哪些*-config.cmake文件以实际文件名为准。4.2 CMake 编译器检测失败报错形态可能是这样CMake Error: Generator MinGW Makefiles could not find any instance of ... The C compiler g.exe is not able to compile a simple test program.如果确定已经安装了 MinGW-w64大概率是下面两个原因之一。第一MinGW 的 bin 目录没在 PATH 里。CMake 与 GCC 本身不是同一个软件CMake 作为控制层需要自己去系统 PATH 中查找编译器。检查方式是打开新终端执行g --version如果提示找不到命令那 PATH 肯定有问题。第二CMake 缓存里残留了之前用 MSVC 或其他工具链生成的配置信息。这种尤其常见于你在这同一个 build 目录下切换过工具链。解决办法很直接把 build 目录整个删除重新执行 cmake 配置命令。CMake 的缓存机制很顽固指望它自动感知工具链变化是不现实的。如果用 Ninja 生成器还要额外确认是否显式指定了 gcc/gcmake -B build -S . -G Ninja ^ -DCMAKE_C_COMPILERgcc ^ -DCMAKE_CXX_COMPILERg因为 Ninja 本身不关心编译器语言环境CMake 在 Ninja 下做编译器探测时如果 PATH 中同时存在 MSVC 环境变量有可能探测到 cl.exe甚至探测失败。4.3 链接阶段的 undefined reference这是 MinGW vcpkg 场景里最丰富多变的报错类型。常见成因有几个。第一种是库的 triplet 与项目参数不一致。比如你手动用vcpkg install fmt装的是x64-windows而项目配置用的是x64-mingw-dynamic。此时 find_package 找到的可能是 MinGW 版本路径下的空配置也可能找到 MSVC 版本但链接失败。记住项目里不要有第二个 triplet全部统一。第二种是查找头文件成功但库文件未链接。你需要在 CMakeLists 里明确加target_link_libraries。比如 fmt 只 include 头文件不作为链接项某些纯头文件库不需要链接但很多库必须链接。第三种是库的链接顺序问题在静态库之间的依赖关系复杂时比较多见。老的链接器对库的顺序敏感原则是“后面的库依赖前面的库”。CMake 生成的链接命令通常已经把 target 依赖关系排好了但如果你通过-DCMAKE_EXE_LINKER_FLAGS手动追加了库就需要小心顺序。排查链接问题可以使用这两个工具用nm -C查看某个库导出的符号确认库文件中是否存在某个报错的符号用objdump -p查看 DLL 的导入导出表。比如报错说找不到fmt::v11::format但你在库文件里能看到这个符号就说明基本链接顺序或参数引用有问题如果库文件里根本找不到该符号那就说明库编译版本与代码版本对不上。4.4 运行时报错找不到 DLL 与 ABI 混用构建成功不代表万事大吉。如果使用的是动态 triplet单元测试或 exe 运行时常会遇到The code execution cannot proceed because fmt.dll was not found.这正是 vcpkg 的 DLL 没有被运行时正确加载。解决办法有几种最后的办法是把相关 DLL 一个个拷贝到 exe 所在目录。简单但比较原始DLL 一多就容易漏。更好的办法是设置环境变量 PATH把 vcpkg 安装目录下的 bin 目录加进去。在 cmd 窗口下可以临时执行set PATH%PATH%;C:\你的路径\build\vcpkg_installed\x64-mingw-dynamic\bin如果是给 IDE 里的运行按钮用就要在 IDE 的运行配置里加这个 PATH。还有个容易忽略的点在命令行启动程序时当前终端的 PATH 可能不是你最后一次设置的 PATH。因为 Windows 下终端是从系统环境变量继承快照的你改了系统 PATH 之后必须新开终端以前已经打开的终端不会识别新变量。这个问题我反复踩过很多次。至于 ABI 混用最隐蔽。现象是程序编译链接都正常但运行时崩溃或者在某些函数调用时行为怪异。这多半是因为你链接的库中有一个是 MSVC 编译的预编译二进制另一个是 MinGW 编译的。要知道 vcpkg 的x64-windowstriplet 和x64-mingw-dynamictriplet 的库会被安装在不同子目录理论上不会混但如果你手动从别的路径链接了库或者项目里同时引入 vcpkg 外的第三方包就可能出现这种 ABI 冲突。遇到诡异崩溃时优先检查所有链接项是否都来自 vcpkg 的同一个 triplet 安装目录。4.5 几个提升效率的小技巧这套流程跑顺之后我总结了几个能明显提升效率的习惯。首先在命令行配置时把VCPKG_TARGET_TRIPLET和 toolchain 参数放进一个 CMake preset 文件里。CMake Presets 是官方推荐的配置复用方式你不用每次敲一大串参数。使用方法是在项目根目录放一个 CMakePresets.json{ version: 3, configurePresets: [ { name: mingw, displayName: MinGW dynamic, generator: MinGW Makefiles, binaryDir: ${sourceDir}/build-mingw, cacheVariables: { CMAKE_BUILD_TYPE: Release, CMAKE_TOOLCHAIN_FILE: $env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake, VCPKG_TARGET_TRIPLET: x64-mingw-dynamic } } ] }之后配置项目只需要cmake --preset mingw其次vcpkg 在 Windows 上执行编译时默认会占用很多并发任务。在 build 阶段加上-j8或更高数值能明显加快编译但注意 vcpkg 的并行数量是独立控制的可以使用环境变量或命令行参数控制避免把 CPU 拉满导致电脑卡死。再者切换工具链或更新编译器版本后不仅要删 build 目录还要记得清理 CMakeCache 之外的历史缓存。最保险的方式是每次重大环境变动后直接用新的输出目录比如 build-mingw、build-msvc 分开命名做到天然隔离。最后还想提一下加速 vcpkg 依赖编译的思路。vcpkg 默认从源码编译依赖这个速度取决于你的 CPU 和网络。如果构建的依赖较多可以挑选一些提供预编译二进制的 port 特性或者考虑把 vcpkg 安装目录下的编译缓存持久化。但这方面配置选项不少初期不用过于追求先把静态 / 动态这条链路跑通再说。写在最后的一些实操心得这次把 vcpkg MinGW 组合跑通之后我的切身体会就是配置本身并不复杂难的是理解每条配置背后的约束关系。triplet 决定了依赖库的 ABItoolchain 文件决定了 CMake 是否能感知依赖编译器路径决定了 CMake 能否成功生成构建系统三者必须咬合在一起缺一个都会以各种奇怪的方式报错。最让我觉得值得的其实是 manifest 模式把依赖声明编进项目后环境的可复制性大幅提升。以前新同事克隆代码后要按文档手动装一堆库经常出现版本不一致的琐碎问题。现在只需要 CMake 配置时自动拉取依赖省掉了大量沟通成本。最后分享一个我后来养成的习惯每次 CMake 配置成功后第一件事是打开 CMakeCache.txt确认CMAKE_TOOLCHAIN_FILE和VCPKG_TARGET_TRIPLET两个变量值与期望一致再继续执行编译。这看起来多了一步但能省下排查构建失败的大量时间。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑