Windows下protobuf 3.33.4源码编译与CMake接入指南 📅 发布时间:2026/9/9 20:55:53 👁 浏览次数: 前阵子需要给一个Windows上的C服务接入protobuf而且是3.33.4这个比较新的版本。本来想图省事直接拖官方release编译好的包下来结果发现要么是dll版本和我的运行环境合不上要么就是链接期一堆符号兼容的破事最后老实走了一遍“源码编译 - 本地安装 - CMake接入”的完整流程。这套流程跑通之后我觉得值得把细节记录下来尤其是那些操作日志里看不到、只有实际踩过才知道的坑。这篇文章的核心内容就两件事如何在Windows上把protobuf 3.33.4从源码编译安装到指定目录以及编译完之后怎么把它干净地接进自己的CMake工程。你可以把它当成一份可以直接照着抄的作业里面所有的CMake命令、参数、目录结构都是我实际跑通过的。如果你当前正在用vcpkg或者Conan管理依赖这篇文章同样能帮你理解protobuf这套构建体系底层的依赖关系排查问题时心里更有数。如果你是第一次在Windows下编译protobuf强烈建议先把第2章关于abseil依赖的部分看完再动手那个环节是大多数人耗时间的重灾区。1. 判断一下你到底需不需要在Windows上自己编译protobuf在动手之前先花两分钟做个需求判断。因为自己编译一套protobuf是有时间成本的abseil加上protobuf两套库在Release模式下完整编一遍机器稍微老一点就是半小时起步加上配置工程、踩坑的时间一个下午很容易就没了。并不是所有场景都需要走这条路。1.1 release装不上需求的几种典型场景官方GitHub的Release页面其实一直提供Windows的预编译包名字通常是类似protoc-3.33.4-win64.zip。这个包里有什么一个protoc.exe、一些include头文件以及对应语言的运行时库文件。对只想用protoc命令把.proto文件转成Java、Python或者C代码的人来说这个包完全够用不需要自己编译。但如果你是在Windows上做C开发情况就复杂了。官方这个win64包里虽然带了C的libprotobuf库文件但它使用的是官方自己的编译配置比如它用的是动态库DLL版本运行时库用的是/MD。如果你的项目恰好需要静态链接、需要/MT运行时、需要把库安装到一个统一的第三方依赖目录里和项目其他库放一起或者你用的是MinGW而不是MSVC那这个官方包就帮不上忙了只能自己编。还有一种常见情况是公司内部有统一的代码规范所有第三方库必须放到指定目录、不能随便把一堆DLL丢进系统目录或者需要给CI/CD环境做一次性的依赖准备。这时候自己编译一份可控的、放在固定路径下的protobuf比每次让同事去官网手动下载要稳定得多。1.2 梳理清楚要编译哪些东西避免做了无用功很多人第一次编译protobuf的时候以为只需要编译protobuf这一个仓库就够了结果配置CMake的时候直接报错找不到absl目录一脸懵。这里先把这个项目的构成说清楚。protobuf这个项目在一开始确实只有一个仓库但从3.21版本开始它把内部底层的数据结构、字符串处理、状态码等基础组件换成了Google内部的Abseil库对标C标准库的增强版。也就是说从3.21往后protobuf在C层面硬依赖abseil-cpp编译protobuf之前必须先把这个依赖编出来并安装好。这也是我个人认为整个流程里最容易翻车的点因为abseil的构建体系非常庞大编译时间甚至比protobuf本体还长。另外还要看你的使用场景。如果只是用protoc生成代码、然后把生成的代码文件直接拷贝到工程里不链接libprotobuf库那其实连abseil都不需要因为protoc.exe本身就是官方编译好的独立程序。但如果你是想在自己的C工程里用#include google/protobuf/message.h这种方式写代码就必须把libprotobuf和它背后的abseil库一起准备好。所以动手之前先问自己三个问题我要不要写C代码我写C代码时能不能接受动态链接我能不能接受把生成代码直接拷进工程而不是链接静态库这几个答案决定你走的是“全量编译”还是“只下载用一用”的路线。这篇文章接下来讲的是全量编译的路线也是最能一劳永逸的路线。做完一次之后所有项目都能复用这份依赖。2. 编译前的关键信息protobuf 33.x与abseil到底是什么关系这一章不直接上命令先讲清楚底层的依赖逻辑。很多人照着网上的教程编译有个非常常见的误区以为protobuf是独立的编完protobuf就行了。在33.x这个版本线里这种想法是行不通的。2.1 为什么protobuf现在离不开abseil简单来说protobuf的C实现内部大量使用了abseil的基础组件比如absl::string_view、absl::Status、absl::flat_hash_map这些。这些组件不是C标准库里有的东西是Google内部专门维护的一套增强库。protobuf作为从Google内部走出来的项目自然就把这套库带了进来作为外部依赖。这意味着链接阶段你的程序不仅需要libprotobuf.lib或DLL还需要abseil提供的一大堆符号。如果你只编译了protobuf而没管abseilCMake配置阶段会直接报错告诉你找不到absl相关的包。如果你强行关闭这个依赖那就只能回到老版本protobuf享受不了新功能。这就像你要装一个高级的家电结果发现它必须配合一个特定型号的电源适配器才能用。适配器本身没什么技术含量但没有它家电就点不亮。abseil就是protobuf的电源适配器躲不掉的。2.2 版本对应关系与工具链选择protobuf和abseil之间是有严格版本对应关系的不能用随便一个版本的abseil去配protobuf 3.33.4。好消息是protobuf项目的CMakeLists.txt里已经写明了它期望的abseil版本。我编译3.33.4时用的abseil是20240722.0这个版本这套组合实测是稳定的。怎么查这个对应关系在你clone下来的protobuf源码目录里找到CMakeLists.txt搜索ABSL_VERSION或者absl相关字段就能看到官方要求的版本号。不要嫌麻烦这一步确认一次后面能少踩两个小时的坑。工具链的选择我的建议非常简单直接如果你用MSVC开发那就老老实实用Visual Studio 2022的生成器来编译。虽然protobuf官方支持MinGW和Clang但这个项目的构建脚本在Windows上默认优化的路径就是MSVC。我自己测试过MSVC编译出来的库在后续链接和运行时兼容性上远好过MinGW折腾半天的结果。至于CMake版本别太老就行3.28以上完全没问题官方要求的最低版本是3.14但新一点总归省事。如果你不是非要在Windows上自己从零编译不可其实也可以用vcpkg一条vcpkg install protobuf:x64-windows就能搞定。但vcpkg的默认配置会自己拉来拉去安装路径、编译选项的控制力比较弱。对于需要精确控制输出目录的团队项目来说自己编译仍然是更可控的方案。这也是我这次选择手动编译的核心原因。3. 完整编译流程从源码到安装目录实测可复现这一章给出我在实际环境中验证过的完整编译流程。下面的命令我以PowerShell环境为例如果你用的是CMD把反引号的续行符换成脱字符^就行。为了路径清晰、避免空格带来的麻烦我把所有第三方库统一放在D:/3rdparty这个目录下你可以按自己的习惯调整。3.1 目录规划与源码准备先建好目录结构让后面所有操作都有明确归属D:/3rdparty/ ├─ source/ # 源码目录 │ ├─ abseil-cpp/ # abseil源码 │ ├─ protobuf/ # protobuf源码 ├─ build/ # 构建目录各种临时文件 ├─ installed/ # 最终的安装目录include/lib/cmake都会放这里为什么要单独建一个installed目录而不是把编译出来的东西混在源码目录里因为protobuf的CMake配置会生成很多中间文件如果安装目录和构建目录混在一起后面升级版本或者切换Debug/Release时非常容易文件错乱。干净隔离后面所有项目都能稳定引用同一个目录下的头文件和库文件。源码从GitHub拉取用--depth 1可以只拉取最新版本加快克隆速度git clone --depth 1 -b v3.33.4 https://github.com/protocolbuffers/protobuf.git git clone --depth 1 -b 20240722.0 https://github.com/abseil/abseil-cpp.git注意protobuf的tag格式是v3.33.4abseil的tag格式没有v前缀直接是20240722.0。网上有些教程会把这两个命令搞混克隆下来之后一看版本不对就先卡住了。3.2 编译安装abseil-cppabseil的CMake配置有几个关键参数需要注意它不像普通库那么“一把梭”。cd D:/3rdparty/source/abseil-cpp cmake -S . -B D:/3rdparty/build/abseil -G Visual Studio 17 2022 -A x64 -DCMAKE_CONFIGURATION_TYPESRelease -DCMAKE_INSTALL_PREFIXD:/3rdparty/installed/abseil -DABSL_BUILD_TESTINGOFF -DABSL_PROPAGATE_CXX_STDON cmake --build D:/3rdparty/build/abseil --config Release --target install -j这里解释几个关键参数的含义理解了之后你自己调整的时候就不会翻车-DABSL_BUILD_TESTINGOFFabseil的测试套件非常庞大我们只是作为依赖使用完全不需要跑它的测试。把这个关掉编译时间能省下三分之一。-DABSL_PROPAGATE_CXX_STDON这个参数的意思是让abseil把C标准版本比如C17、C20传递给被编译的protobuf。如果你把它设成OFF后面protobuf编译时可能出现C标准不一致的奇怪问题编译出来的代码行为诡异却找不到原因。这个参数对protobuf 33.x来说非常重要。-DCMAKE_CONFIGURATION_TYPESRelease这一行直接把多配置生成器的配置类型锁死成Release。如果你想在Windows上同时要Debug和Release两套库就别加这个限制但那样编译时间会翻倍。我的建议是先只编ReleaseDebug版本要不要后面再说绝大多数运行环境Release够用。编译过程会比较久中途可能看起来像卡住了尤其是abseil在编译一些模板密集的组件时CPU占用高、输出界面却半天不动一下。这不是死机是它在做大量模板实例化耐心等就行。编完之后在D:/3rdparty/installed/abseil目录下会看到include和lib/cmake等子目录其中lib/cmake/absl目录里那些abslConfig.cmake之类的文件就是后面protobuf找依赖时需要用到的线索。3.3 编译安装protobufabseil安装完成之后终于轮到主角登场。cd D:/3rdparty/source/protobuf cmake -S . -B D:/3rdparty/build/protobuf -G Visual Studio 17 2022 -A x64 -DCMAKE_INSTALL_PREFIXD:/3rdparty/installed/protobuf -Dprotobuf_BUILD_TESTSOFF -Dprotobuf_BUILD_SHARED_LIBSOFF -Dprotobuf_MSVC_STATIC_RUNTIMEOFF -Dprotobuf_BUILD_PROTOC_BINARIESON -Dabsl_DIRD:/3rdparty/installed/abseil/lib/cmake/absl cmake --build D:/3rdparty/build/protobuf --config Release --target install -j这里的几个protobuf专属参数一个个拆开说-Dprotobuf_BUILD_TESTSOFF跟abseil同理关掉测试能省不少时间。protobuf自带的测试集如果打开编译完还得跑一轮测试对使用来说意义不大。-Dprotobuf_BUILD_SHARED_LIBSOFF这是决定静态链接还是动态链接的开关默认值其实是OFF也就是静态库。我强烈建议保持静态库。原因有两个一是静态库部署方便目标机器不用额外装DLL二是protobuf官方在Windows平台对DLL版本的支持一直有一些符号导出方面的兼容性讲究静态库反而省心。-Dprotobuf_MSVC_STATIC_RUNTIMEOFF这个参数控制的是MSVC运行时库模式。OFF表示用动态运行时库/MDON表示用静态运行时库/MT。这里必须和你的项目保持一致。如果你的项目用的是/MDVisual Studio默认就是这个参数就不要动如果你项目的CMakeLists里设置了/MT这里就要改成ON。这个参数和后面第5章的链接错误高发区直接相关先留个印象。-Dprotobuf_BUILD_PROTOC_BINARIESON编译protoc.exe。即使你只想用库我也建议把这个打开因为后续用CMake生成代码时需要一个protoc可执行文件放在一起更方便。-Dabsl_DIR...这条是给CMake指明abseil的安装位置。因为abseil安装后没有注册到系统全局必须在这个参数里显式指定protobuf的构建脚本才能在配置阶段找到absl的CMake配置。配置阶段如果顺利CMake会输出一堆检查结果最后出现Generating done之类的提示。编译阶段会比abseil快一点但也不轻松。编完后在D:/3rdparty/installed/protobuf目录下会看到bin/protoc.exe include/google/protobuf/... # 一堆头文件 lib/libprotobuf.lib lib/libprotoc.lib lib/cmake/protobuf/... # CMake包配置3.4 安装结果验证编译安装不是“看起来装了”就行的我每次都会做两个简单的验证第一步验证protoc版本和基本功能D:/3rdparty/installed/protobuf/bin/protoc.exe --version如果返回libprotoc 3.33.4说明可执行文件没问题。第二步写一个最简单的proto文件做个端到端测试syntax proto3; package demo; message TestMsg { int32 id 1; string name 2; }然后执行D:/3rdparty/installed/protobuf/bin/protoc.exe --cpp_out. test.proto如果目录下生成了test.pb.h和test.pb.cc并且打开文件看头部版权声明版本是3.33.4那说明protoc工作正常。到这一步protobuf编译安装这件事就基本尘埃落定了。4. 接入你的CMake项目find_package与protoc代码生成库编好了接下来就是怎么把它用起来。很多新手在这一步开始迷茫因为protobuf的CMake接入方式有几种写法每种背后的逻辑还不一样。我在实践中整理出两种最常用的接入方式分别对应不同的项目结构需求。4.1 第一种用法直接链接静态库如果项目里已经手工生成好了.pb.cc和.pb.h文件比如你用命令行跑过protoc那只需要在CMakeLists里让项目找到protobuf包然后链接libprotobuf就行。cmake_minimum_required(VERSION 3.20) project(proto_demo) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 指定protobuf安装路径 set(protobuf_DIR D:/3rdparty/installed/protobuf/lib/cmake/protobuf) find_package(protobuf CONFIG REQUIRED) add_executable(demo main.cpp test.pb.cc test.pb.h) target_link_libraries(demo PRIVATE protobuf::libprotobuf)这种方式简单直接前提是.pb.cc文件已经存在。它的问题在于如果proto文件改了你得手动重新跑一次protoc命令再重新生成代码。项目小了无所谓proto文件一多就很容易漏刷新出现“改了半天不生效”的鬼问题。4.2 第二种用法用protobuf_generate_cpp自动生成代码推荐的方式是用CMake自带的protobuf_generate_cpp函数它会在构建时自动调用protoc把.proto文件生成对应的.cpp和.h并且自动纳入编译流程。这样源文件里的proto变更之后只需重新构建项目不需要手动跑命令。cmake_minimum_required(VERSION 3.20) project(proto_demo) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(protobuf_DIR D:/3rdparty/installed/protobuf/lib/cmake/protobuf) find_package(protobuf CONFIG REQUIRED) protobuf_generate_cpp(GENERATED_SRCS GENERATED_HDRS test.proto ) add_executable(demo main.cpp ${GENERATED_SRCS} ${GENERATED_HDRS}) target_link_libraries(demo PRIVATE protobuf::libprotobuf) # 把生成的头文件目录加入包含路径 target_include_directories(demo PRIVATE ${CMAKE_CURRENT_BINARY_DIR})这里有个容易踩的坑protobuf_generate_cpp生成的源码默认放在CMAKE_CURRENT_BINARY_DIR目录下如果你忘了target_include_directories加上这个目录编译时会报找不到test.pb.h。这个错误信息很常见很多人一开始还以为是protobuf安装路径配错了。如果你的proto文件不是放在项目根目录而是单独的proto/子目录那protobuf_generate_cpp的第一个参数还需要传给protoc的导入路径写法有些不同后续文章可以专门展开。我这里先给最简单的情况保证第一次跑能成功。4.3 一个最小可运行的Demo最后给你留一个完整的Demo工程结构放在D:/proto_demo可以直接复制跑起来D:/proto_demo/ ├─ CMakeLists.txt ├─ test.proto └─ main.cppCMakeLists.txt内容就用上面第4.2节的版本。test.proto就用第3.4节那个简单的消息定义。main.cpp写一个最基础的创建消息、赋值、序列化、反序列化的例子#include iostream #include string #include test.pb.h int main() { demo::TestMsg msg; msg.set_id(42); msg.set_name(hello protobuf); std::string data; msg.SerializeToString(data); demo::TestMsg parsed; parsed.ParseFromString(data); std::cout id parsed.id() , name parsed.name() std::endl; return 0; }构建命令cd D:/proto_demo cmake -S . -B build -G Visual Studio 17 2022 -A x64 cmake --build build --config Release跑起来的输出应该是id42, namehello protobuf。如果这一步能顺利通过说明从编译到接入的全部流程已经通了。整个链路验证完毕之后你在自己项目里接其它proto文件只是重复这个套路而已。5. 编译之后最常遇到的坑和排查记录整个流程跑下来我自己也踩了不少坑。这里挑几个典型问题尤其是那种报错信息看着莫名其妙、实际上原因非常单一的情况给你做一个速查手册。以后你如果遇到类似报错不用再重新走一遍排查流程。5.1 运行时提示找不到protobuf.dll如果你没有用-Dprotobuf_BUILD_SHARED_LIBSOFF而是用了默认的动态库版本编译的时候程序可能一切正常一运行就弹窗说“找不到libprotobuf.dll”。这是因为程序生成的exe在运行时需要去加载DLL而DLL又没有放到exe同目录或者系统PATH里。解决方案有两个要么把DLL复制到exe同目录要么把D:/3rdparty/installed/protobuf/bin加到系统PATH里。但我个人最推荐的方式还是从源头避免——直接编静态库。静态库链接进exe后运行时完全不再需要单独的DLL部署的时候直接拷贝exe一个文件就行省掉一堆环境问题。5.2 MSVC运行时库MT与MD不一致的链接错误这个错误的典型症状是链接时报一堆unresolved external symbol符号名里还带着__imp_前缀。根本原因就是编译protobuf时用的运行时库模式和你项目里用的不一致。protobuf如果用/MD编的你项目里却用/MT链接MSVC的运行时库两套符号混在一起必然报错。这个问题的排查方向很明确检查你项目的CMakeLists里有没有设置/MT如果有的话编译protobuf时-Dprotobuf_MSVC_STATIC_RUNTIME必须同步设为ON两边一致才能消掉这类错误。这里没有捷径只能保证protobuf编译使用的运行时模式和消费方项目保持一致。5.3 Debug和Release混用引发的LNK2038LNK2038这个错误在MSVC下非常经典错误信息里会列出RuntimeLibrary的Mismatch。比如你的库是用Release配置编的但项目以Debug模式链接运行库的类型检查就会对不上。还有_ITERATOR_DEBUG_LEVEL不匹配也是Debug/Release混用时常见的连锁反应。排查思路很简单你的消费方项目如果编译Debug配置那就去编译一份Debug版本的protobufRelease项目配Release版本。不要觉得“反正代码一样版本凑合一下也能用”MSVC的调试迭代器在这类场景下就是专门给你使绊子的绕不过去。所以我建目录时特意在D:/3rdparty里规划了不同配置的输出位置比如installed/protobuf-rel和installed/protobuf-debug。Debug需要的时候再去编一份路径隔开永远不混用。5.4 protoc版本与库版本不一致的噩梦这个问题比较隐蔽症状也很迷惑代码编译能过运行时解析数据却出现字段丢失、错乱甚至直接崩溃。原因很多时候是生成.pb.cc和.pb.h时用的protoc是3.30版本但项目链接的libprotobuf是3.33.4版本。不同小版本之间生成的代码通常不保证二进制兼容一旦不兼容就会在运行时以诡异的方式暴露出来。排查方法是查看生成的.pb.h文件头部注释里的版本信息再和链接库的版本对比。我见过太多次“一切正常但跑起来就崩”的bug最后发现就是版本不一致。所以从第一天起就养成习惯明确项目用哪个版本protobufprotoc和libprotobuf必须是同一个版本发布包里的产物中间不许混搭。在这些坑都踩平之后protobuf 3.33.4在Windows平台就可以稳定使用了。对我来说自己手动构建的核心收益不在于省了多少时间而在于整个依赖链路的版本、配置、路径都尽在掌握。后面你的团队如果也需要在Windows上引入protobuf可以参考这套流程先在本地跑通一遍再把目录整体拷给其他同事省去每个人各自踩坑的时间。