资讯动态

Windows下vcpkg+MinGW+CMake构建C++项目实战指南

发布时间:2026/10/20 5:12:04 来源:尧图企业网站定制
1. 我为什么最终把构建链定格在“vcpkg 管依赖、MinGW 管编译”vcpkg MinGW 在 Windows 下构建 CMake 项目听起来像是日常操作里最简单的组合但真正把这套链路跑顺之后我才意识到它坑的从来不是“不会用”而是“默认配置和你的实际编译器不一致”。我之前维护一个跨平台 C 工具代码本身不复杂依赖倒是一堆JSON 解析、HTTP 请求、日志库随便一个都是开源里最常见的选择。Windows 上本来用 MSVC 也顺可项目里另一个模块必须用 GCC 侧的 ABI两边一碰就是符号差异、运行时库不匹配最后整个构建链被我推翻重来。这轮折腾下来我的结论很清晰vcpkg 负责把第三方依赖按指定工具链编译好MinGW 负责把项目代码和这些依赖链接成 exeCMake 只做中间的调度。三者各管一摊边界划清楚之后Windows 上的构建体验和 Linux 差距就没那么大了。1.1 先说我碰到的第一个坎依赖的编译器对齐大多数人第一次用 vcpkg都是直接vcpkg install fmt完事默认 triplet 是x64-windows也就是 MSVC 编译的版本。随后你切到 MinGW 去cmake -G MinGW Makefiles ..链接阶段十有八九报错错误长得像这样undefined reference to fmt::v8::detail::format_impl(...)当时我一度以为是链接顺序有问题后来才发现根本不是。vcpkg 默认装的库是 MSVC 的lib格式MinGW 的链接器根本不吃这一套两者连 ABI 都不一致强行链接只会得到一堆 undefined reference。要解决这件事不是调整编译选项而是从一开始就让 vcpkg 以 MinGW 的 triplet 来编译依赖。这件事给我的教训是在 Windows 上vcpkg 并不是“装好就能用”它默认服务于 MSVC 工具链。如果你像我一样需要在 MinGW 下构建第一件事就是把默认 triplet 改成对应 MinGW 的版本而且要在一开始就改否则缓存里全是 MSVC 的二进制文件后面清都费劲。1.2 vcpkg 和 MinGW 在项目里各负责什么把边界说清楚能少走很多弯路。vcpkg包管理器负责下载源码、编译、安装第三方库。它产出的是库文件和 CMake 配置文件。它不做项目本身的编译它的存在就是为了让你不用手工去编译 curl、fmt 这种几十个依赖。MinGW编译器工具链负责把项目源码和第三方库的二进制一起编译、链接成可执行文件。MinGW-w64 里包含了 GCC、G、windres、dlltool等工具。CMake构建系统生成器负责读取 CMakeLists.txt调用编译器并且把 vcpkg 的库路径通过配置文件暴露给编译和链接过程。这三者的关系可以类比成装修vcpkg 是建材市场先把瓷砖、水泥按你的规格准备好MinGW 是施工队负责把材料砌成房子CMake 是项目经理告诉施工队“材料在哪个仓库、按什么图纸施工”。如果项目经理指错仓库比如让 MinGW 施工队去取 MSVC 仓库里的水泥那房子砌到一半必然塌。所以后续所有配置核心就一句话让 CMake 能在 MinGW 编译时精确地找到 vcpkg 为 MinGW 生成的库。2. 开工前先把两条腿站住MinGW 环境与命令行工具的版本核对很多人在这套组合上翻车不是配置写错而是环境本身没站住。MinGW 装了一堆版本CMake 装了但命令行里调不到Git 没有把所有子模块拉全。这些基础问题会让后面的每一步都像是“玄学失败”。2.1 这四件东西要按顺序装我最后稳定运行的环境是按这个顺序准备的MinGW-w64我建议选带 POSIX 线程模型的版本文件解压到C:\mingw64。解压后把C:\mingw64\bin加入系统 PATH。这个版本能兼顾 C11 以上线程库的使用比老版 win32 线程模型省心。CMake安装新版即可装完确保命令行能直接敲cmake --version。这步很容易漏因为很多人安装时把“Add CMake to PATH”那个勾取消了。Gitvcpkg 本体是 Git 仓库后续更新靠它。Ninja严格来说不是必须的但在 Windows 上用 MinGW 配合 Ninja 构建比MinGW Makefiles生成器更稳并行速度也更快。可以单独下载 ninja 然后扔进某个已加入 PATH 的目录。版本核对可以这样打gcc --version cmake --version git --version ninja --version四行命令全都输出版本号才说明环境起步正常。2.2 命令行里我习惯先跑三条探测命令在开始正式配置前我会先确认编译器能编译一个最小程序防止后面把“编译器坏了”误判成“CMake 配置错误”。echo int main(){return 0;} test.cpp g test.cpp -o test.exe如果这一步通过接着检查 vcpkg 克隆git clone https://gitee.com/mirrors/vcpkg.git # 或者你网络环境可用的镜像源最后用 CMake 自带的探测确认 Ninja 能找到 GCCcmake -G Ninja --system-information | grep -i gcc这一步要看到类似CMAKE_CXX_COMPILER: FILEPATH C:/mingw64/bin/g.exe的输出。如果这里显示的是 MSVC 或者路径为空后面的配置全都会白搭。环境干净了才有资格谈 vcpkg 的配置。3. vcpkg 侧不需要什么复杂配置把默认 triplet 钉在 MinGW 上就行vcpkg 初始化本身不复杂难的是理解 triplet。很多教程直接把VCPKG_DEFAULT_TRIPLET说成是“必须要设的环境变量”但没讲清楚它影响的到底是哪一步。这一节我把自己的配置方式写全顺便解释为什么这样设。3.1 初始化 vcpkg 与三个环境变量我在C:\vcpkg下拉取仓库后执行cd C:\vcpkg bootstrap-vcpkg.bat这个脚本会生成vcpkg.exe。随后设置这三个环境变量set VCPKG_ROOTC:\vcpkg set VCPKG_DEFAULT_TRIPLETx64-mingw-dynamic set PATHC:\vcpkg;C:\mingw64\bin;%PATH%VCPKG_DEFAULT_TRIPLET的作用是把 vcpkg 的默认目标平台从x64-windows改成x64-mingw-dynamic。这样我每次执行vcpkg install fmt不需要追加--triplet参数vcpkg 就会自动用 GCC 编译一份动态链接库。很多人问为什么要额外指定VCPKG_ROOT其实主要为了后续 CMake 里引用$env{VCPKG_ROOT}比较方便。我的原则是所有路径都写成环境变量引用不要到处硬编码盘符路径这样项目换机器时不用翻文件逐个改。3.2 dynamic 和 static 的真正取舍x64-mingw-dynamic和x64-mingw-static的区别不是简单的“一个快一个慢”而是链接期和运行期的完整策略差异维度x64-mingw-dynamicx64-mingw-static库形态DLL 导入库静态库 .a最终 exe 体积小运行时依赖 DLL大但能单文件分发调试时替换依赖方便换 DLL 即可需重新编译分发场景适合开发迭代适合交付给没有装环境的用户我平时开发阶段用 dynamic因为迭代快改了一个 DLL 不需要把所有库重新链接一遍。但真的要给同事跑演示版时我往往再切到 static 构建一份单文件 exe。注意这里说的链接策略只影响项目最终如何链接 MinGW 侧的库和 vcpkg 里其他依赖无关。只要你始终统一用同一个 tripletCMake 里依赖查找就不会因为“动态库还是静态库”的问题反复报错。还有一个很容易踩的细节如果你之前用默认x64-windows安装过某个包再切到x64-mingw-dynamic旧缓存目录里的文件并不会自动消失。建议把C:\vcpkg\buildtrees和C:\vcpkg\packages清一遍再重新装。否则偶尔会出现 vcpkg 认为包已安装但编译器实际拿到的还是旧 ABI 文件的情况。4. CMake 接住 vcpkg 的关键一针toolchain 文件而不是路径拼接vcpkg 官方文档给出的接入方式是给 CMake 传-DCMAKE_TOOLCHAIN_FILEC:/vcpkg/scripts/buildsystems/vcpkg.cmake。这句话本身没错但它带来的一个隐蔽问题是toolchain 文件会在 CMake 检测编译器之前就被加载你如果同时又在里面追加了编译器路径、编译选项很容易互相覆盖。所以我的做法是toolchain 文件完全交给 vcpkg 默认脚本编译器相关的配置用 CMakePresets 来管理。4.1 一种干净接法CMakePresets 环境变量项目根目录下我维护一份CMakePresets.json内容如下{ version: 3, configurePresets: [ { name: mingw-debug, generator: Ninja, binaryDir: ${sourceDir}/build/mingw-debug, toolchainFile: $env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake, cacheVariables: { CMAKE_C_COMPILER: gcc, CMAKE_CXX_COMPILER: g, CMAKE_BUILD_TYPE: Debug }, environment: { PATH: C:/mingw64/bin;C:/vcpkg;$penv{PATH} } }, { name: mingw-release, generator: Ninja, binaryDir: ${sourceDir}/build/mingw-release, toolchainFile: $env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake, cacheVariables: { CMAKE_C_COMPILER: gcc, CMAKE_CXX_COMPILER: g, CMAKE_BUILD_TYPE: Release } } ] }这里每一个字段都有必要说下generator是 Ninja。Ninja 在 Windows 上对 MinGW 的支持很干净不会像MinGW Makefiles一样偶尔卡在sh相关命令上。toolchainFile用的是$env{VCPKG_ROOT}环境变量而不是直接写C:/vcpkg/...这样换机器、换目录都不用改配置。CMAKE_C_COMPILER和CMAKE_CXX_COMPILER直接写gcc、g依赖 PATH 去寻找。这比写死C:/mingw64/bin/gcc.exe灵活因为团队里可能有人装的路径不同。配置完成后构建操作就简化为两条命令cmake --preset mingw-debug cmake --build build/mingw-debug4.2 为什么我不推荐在 CMakeLists 里写死 vcpkg我在网上看到过不少教程直接在CMakeLists.txt里写set(CMAKE_TOOLCHAIN_FILE C:/vcpkg/scripts/buildsystems/vcpkg.cmake)这种写法在单机项目里能用但一旦项目要分享、要跨平台、要用 CI就是灾难。CMakeLists.txt是项目构建逻辑的核心里面的路径应当是相对路径和逻辑规则而不是绑定某台机器的绝对路径。toolchain 文件是“环境层”的东西应该由调用方传入。另外还有一个实际原因CMAKE_TOOLCHAIN_FILE是在编译器探测之前加载的如果你在一个CMakeLists.txt里写了它后面又打算用双编译器构建两种产物CMake 会非常难切换。用 CMakePresets 管理则可以随时切--preset mingw-debug还是--preset msvc-debug互不干扰。5. 让项目第一次完整站起来一个双依赖小项目的全过程配置和环境都讲完了我拿一个非常小的项目做完整演示。这个项目只依赖两个包fmt和curl。功能也简单用fmt打印一条信息再用curl发起一个简单请求。目的是把从 vcpkg 安装依赖到最终运行 exe 的完整链路走一遍。5.1 配置 vcpkg manifest现代 vcpkg 更推荐 manifest 模式就是在项目根目录放一个vcpkg.json{ name: mingw-demo, version: 0.1.0, dependencies: [ fmt, curl ] }这个文件的作用是让项目自带依赖声明。任何人 clone 仓库后执行 CMake 配置时vcpkg 的 toolchain 文件会自动读取vcpkg.json并安装依赖。比起手动在命令行敲vcpkg install fmt再换台机器重新敲一遍manifest 模式明显更适合团队协作。我的CMakeLists.txt长这样cmake_minimum_required(VERSION 3.25) project(MingwDemo) find_package(fmt CONFIG REQUIRED) find_package(CURL CONFIG REQUIRED) add_executable(hello_demo main.cpp) target_link_libraries(hello_demo PRIVATE fmt::fmt CURL::libcurl)注意两点find_package(CURL CONFIG REQUIRED)依赖 vcpkg 提供CURLConfig.cmake。不同版本的 vcpkg 里curl 的 CMake 包名有时是CURL有时是libcurl如果find_package报错先去安装目录的share/curl里看看到底生成了什么名字的配置文件。链接顺序方面MinGW 的链接器对顺序敏感把fmt::fmt写在前面CURL::libcurl写在后面可以减少一部分“undefined reference”的坑。5.2 从配置到编译、再到跑出结果的完整命令序列环境变量准备好后按顺序执行cd C:\projects\mingw-demo vcpkg install --triplet x64-mingw-dynamic cmake --preset mingw-debug cmake --build build/mingw-debug第一次执行时vcpkg 会因为vcpkg.json自动开始编译fmt和curl。fmt 通常很快curl 依赖一堆 zlib、openssl、nghttp2 之类的子依赖耗时会比较长。屏幕上会滚动大量编译日志看到类似Updating port curl和Building curl[core]:x64-mingw-dynamic的字样就说明 vcpkg 正在干活。构建完成后执行.\build\mingw-debug\hello_demo.exe如果一切正常程序会输出 fmt 格式化后的文字并完成一次 curl 请求。到这里vcpkg MinGW CMake 的链路就第一次完整跑通了。跑通后我还会再做一个验证在 dist 目录下用ldd hello_demo.exe查看动态库依赖确认 DLL 都指向 MinGW 和 vcpkg 的目录而不是意外依赖了 MSVC 的msvcp140.dll。这一步非常关键否则程序拷给别人一运行就报“缺少 VCRUNTIME140.dll”又是另一轮痛苦。6. 实战中高频翻车的五个错误附完整排查链路这部分是干货中的干货。我在同一套组合下反复折腾过好几轮把最容易遇到的五类问题整理出来。每一类我都给了一个“从现象到根因再到修复”的完整思路很多人解决问题时只看表象结果错了方向。6.1 “找不到包”——多半不是包不存在报错现象CMake Error at CMakeLists.txt:1 (find_package): Could not find a package configuration file provided by fmt很多人第一反应是去改CMAKE_PREFIX_PATH手动把 vcpkg 的installed/x64-mingw-dynamic/share目录加进去。这能解决问题但属于绕过问题。我建议的排查顺序先看vcpkg install的输出是否真的把包装到了installed/x64-mingw-dynamic。确认 CMake 配置时是否真的加载了 vcpkg 的 toolchain 文件。跑cmake --preset mingw-debug后再看CMakeCache.txt搜CMAKE_TOOLCHAIN_FILE。确认 triplet 是否和安装时一致。如果 vcpkg 装的是x64-windows而 CMake 配置的是x64-mingw-dynamic找不到配置文件的报错几乎必然出现。这类问题大多数不是“包不存在”而是 toolchain 文件没生效或 triplet 不一致。6.2 链接阶段一堆 undefined reference——先查 triplet报错现象[50%] Linking CXX executable hello_demo.exe .../libcurl.dll.a: undefined reference to curl_easy_init根因分析链接器找到了库文件但库里面的导出符号对不上。最常见的原因是项目编译时编译器认为curl是 MSVC 版本或者把动态库和静态库的导入文件混用了。我的处置方式删除整个build/mingw-debug目录避免 CMake 缓存里残留旧的编译器检测信息。确认环境变量VCPKG_DEFAULT_TRIPLET是x64-mingw-dynamic。重新执行 vcpkg 安装用vcpkg list查看当前实际安装的 triplet。再次配置构建。这四步走下来绝大多数 undefined reference 都消失。如果还出现就要检查是不是某个依赖库本身只发布了 MSVC 版本需要去它的 CMake 配置里看源码和导入库的关系。6.3 Ninja 和 GCC 的版本错位报错现象CMake Error: CMAKE_C_COMPILER not set, after enabling C或者The C compiler identification is unknown根因分析这个报错很迷惑因为编译器明明装了。实际上 CMake 找不到gcc要么是 PATH 里没有C:\mingw64\bin要么是 CMakePresets 里写了CMAKE_C_COMPILER: gcc但当前环境变量被某个脚本覆盖了。我的处理办法在配置前先手动执行where gcc确认能输出路径。如果输出为空则在当前终端里重新设置 PATHset PATHC:\mingw64\bin;C:\vcpkg;%PATH%再重新配置。Ninja 本身不包含编译器它只是构建调度器CMAKE_C_COMPILER最终要能解析成一个真实存在的可执行文件。6.4 MinGW 拿到了 MSVC 的静态库报错现象...\curl.lib : warning LNK4003: 无效的对象文件格式严格来说如果 toolchain 配置正常MinGW 不会去读.lib文件。但有一种情况很阴险某些第三方库的 CMake 配置文件里把库位置写成了${VCPKG_INSTALLED_DIR}/x64-windows/lib/curl.lib而你没有重新安装 MinGW triplet 的版本。此时 MinGW 链接器被迫读取 MSVC 格式的静态库然后报出各种看不懂的错误。我的经验遇到这类报错不要死磕库文件格式而是回头检查有没有残留的x64-windows缓存。直接删除C:\vcpkg\installed\x64-windows或者单独重新安装一次vcpkg install curl --triplet x64-mingw-dynamic。清理干净再构建问题通常不会再出现。6.5 路径空格和 PATH 顺序导致的近乎玄学的失败报错现象配置时 CMake 提示找不到某些工具但手动执行命令又能运行或者构建到一半突然“系统找不到指定的文件”。根因分析vcpkg 和 MinGW 如果安装在带空格的路径下比如C:\Program Files\vcpkgtoolchain 脚本里大量路径变量会被错误拆分。另外 PATH 顺序也有讲究如果C:\Windows\System32排在C:\mingw64\bin前面某些环境下where gcc可能指向系统自带的假 GCC 脚本。我的建议路径规划组件推荐路径原因vcpkgC:\vcpkg路径短、无空格、方便记忆MinGWC:\mingw64同上项目源码C:\projects\xxx避免OneDrive等云同步目录干扰构建构建目录项目内build/CMake 自动创建方便清理所有路径都放在 C 盘根目录附近不是洁癖而是 Windows 工具链对空格的处理确实简陋省掉这个因素能减少一半的随机性失败。6.6 排错通用思路总结我在这个组合上踩了几轮之后总结出一个固定思考顺序先看环境变量再看构建缓存最后才怀疑代码问题。具体来说报错出现后第一步永远是确认VCPKG_DEFAULT_TRIPLET和VCPKG_ROOT有没有被终端环境覆盖。第二步是删除build目录重新配置CMake 缓存有记忆很多修改不重建缓存等于白改。第三步才去看CMakeCache.txt里具体某个变量值比如CMAKE_CXX_COMPILER:FILEPATH是否还是老路径。如果还解决不了最后打开 vcpkg 的安装目录看库文件到底是.a、.dll.a还是.lib基本就能定位是哪个环节的格式错配。这套排查链路我屡试不爽比对着错误信息搜答案高效得多。毕竟报错信息只是结果构建链路上每一个配置都可能把它引向不同方向。我个人最终把这份配置固化到自己的项目模板里之后每次开新项目直接把CMakePresets.json和vcpkg.json拷过去改个项目名就能开跑。如果你也经常需要在 Windows 上同时维护多编译器构建的项目建议按这套方式把环境沉淀成模板而不是每次从零配置能省下大量重复排查的时间。

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

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

免费获取报价 →
↑