news 2026/9/2 19:57:50

VS2019下编译使用Protobuf 3.8.0:C++序列化与反序列化实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
VS2019下编译使用Protobuf 3.8.0:C++序列化与反序列化实战

简介:这是一份面向 C++开发者的 protobuf 3.8.0 在 Visual Studio 2019 环境下的完整使用案例资源。资源不仅提供官方库文件,还包含可运行的演示工程与配套源码,清晰演示了从创建 .proto 文件定义 Person 等消息结构,到调用 protoc 编译器生成 message.pb.h 与 message.pb.cc,再到通过零拷贝流将对象序列化到字符串或内存、以及从字节流反序列化回对象的全过程,适合需要快速上手 ProtoBuf 的初、中级 C++ 开发者。压缩包共含 688 个文件,以 cc 源码、h 头文件、proto 定义文件为主体,同时包含 VS 工程与解决方案文件、编译中间产物、少量可执行的 protoc 工具以及调试信息,总计约 4.72MB,目录结构清晰,便于按模块直接嵌入现有项目。已有 1817 人学习下载。借助这份资源,读者可省去自行编译配置环境的繁琐过程,直接参照完整案例搭建项目,同时理解跨平台数据交换与高效二进制序列化的核心用法,为后续网络通信、数据存储等场景提供可直接复用的实现参考。

1. 项目概述与环境选型

1.1 为什么选 protobuf 3.8.0 + VS2019

做 C++ 服务端开发的兄弟应该都清楚,只要是涉及跨语言、跨平台的数据交换,Protocol Buffers(以下简称 protobuf)基本上是绕不开的标配。我在实际项目里用 protobuf 很多年了,从 2.x 一直追到 3.x,客观说,3.8.0 这个版本属于 proto3 语法稳定之后、性能和安全修复比较完善的阶段,没有早期 3.x 版本那么多坑,也比后边 3.20+ 的 cmake 配置变化小,对于在 Windows + VS2019 环境下做 C++ 开发来说,是相当省心的选择。

VS2019 同样是当前存量项目里占有率很高的 IDE。很多朋友在公司里接手的老项目就是 VS2019 建的,自己本地又装了新版本,结果开发的时候经常碰到工程格式兼容、工具集版本不一致的问题。用 VS2019 搭配 protobuf 3.8.0,正好是网上资料最丰富、教程最多的组合,遇到问题基本都能搜到现成答案,省得自己折腾。

这篇内容我按实际项目的落地顺序来写:怎么编译出 protobuf 库、怎么写 .proto 文件、怎么生成 C++ 类、怎么在 VS2019 里配置工程、最终怎么在代码里完成序列化和反序列化。新手照着一路点下来能跑通,老手也可以直接跳到常见问题部分,看看有没有自己之前没注意到的坑。

1.2 整体方案设计思路

protobuf 在 C++ 项目的使用链路其实可以拆成四个阶段:

  • 用 .proto 文件描述数据结构,这是与语言无关的定义层;
  • 用 protoc 编译器生成目标语言的类代码(C++ 生成 .pb.h 和 .pb.cc);
  • 在 VS2019 工程里配置 include 路径、lib 路径和链接库;
  • 在业务代码里调用生成类完成序列化(对象转字节流)和反序列化(字节流转对象)。

很多人卡住的位置不在业务代码,而在前两步:要么不会编译 protobuf 的 C++ 库,要么不知道 protoc 生成的代码应该放到工程的什么位置。下面我把每个环节的细节逐步展开,重点说那些编译不过、链接失败时容易忽略的地方。

2. 从源码编译 protobuf C++ 库

2.1 准备工作:需要下载哪些东西

要再强调一次,protobuf 的 C++ 运行时库并不是装个绿色版就能直接用的,你得拿源码配合 CMake 自己编,或者找现成的预编译包。3.8.0 那个年代官方没有直接提供 VS2019 对应的预编译二进制,所以自己编译是主流做法。

需要准备的材料如下:

组件说明获取方式
protobuf 源码 v3.8.0包含 protoc 源码、C++ 运行时库、cmake 配置GitHub 官方仓库 release 页面下载 protobuf-cpp-3.8.0.zip
CMake 3.14+用于生成 VS2019 工程文件cmake.org 下载安装包,安装时勾选“添加 CMake 到系统 PATH”
VS2019需要安装“使用 C++ 的桌面开发”工作负载Visual Studio Installer 中修改工作负载

这里有个经验:在 GitHub 下载源码包的时候,一定要认准protobuf-cpp-3.8.0.zip,不要下成protobuf-java-3.8.0.zip或者其他语言的包。C++ 这个包解压之后,根目录下能看到cmake文件夹和CMakeLists.txt,这才是我们需要的。

2.2 CMake 生成工程与编译步骤

protobuf 官方推荐直接用 CMake 构建。我习惯在源码根目录外建一个独立的 build 目录,避免污染源码目录:

# 源码解压到 E:\protobuf\protobuf-3.8.0 # 进入源码目录,创建 build 目录 cd E:\protobuf\protobuf-3.8.0 mkdir build cd build # 生成 VS2019 工程,注意指定 x64 还是 Win32 cmake .. -G "Visual Studio 16 2019" -A x64 -DCMAKE_INSTALL_PREFIX=E:\protobuf\install # 编译并安装,Debug 和 Release 可以都编,或者按需选择 cmake --build . --config Release cmake --install .

这一步要说明几个关键点:

-A x64指定生成 64 位工程,这个必须和你后续主工程的平台一致。如果主工程是 Win32,就写-A Win32,不然后面链接阶段会非常痛苦,一堆 LNK2019。

-DCMAKE_INSTALL_PREFIX指定安装目录,编译完后 include、lib、exe 都会集中输出到这个目录下,方便我们后面配置 VS2019 工程时引用。我的习惯是统一装到E:\protobuf\install,这样头文件在E:\protobuf\install\include,库文件在E:\protobuf\install\lib,protoc 编译器在E:\protobuf\install\bin

编译过程如果一切顺利,几分钟就能完事。如果中途报错,大概率是 CMake 版本太老,或者 Windows SDK 组件没装全。VS2019 安装器里记得勾选 Win10 SDK,哪怕你觉得不写 Windows 底层代码用不上,编译第三方库时经常会碰到头文件缺失,这时候再补装就有点浪费时间了。

2.3 编译产物验证

安装完成后,检查一下E:\protobuf\install目录:

  • bin\protoc.exe:编译器,负责把 .proto 转成 C++ 代码;
  • include\google\protobuf\...:运行时头文件;
  • lib\libprotobuf.liblibprotobufd.lib:静态库(d 结尾是 Debug 版)。

这里有个细节要注意:protobuf 3.x 在 Windows 下默认生成的静态库,Debug 和 Release 是分开的。链接的时候如果 Release 工程链了 debug 库,或者反过来,都会出 LNK2038 或者运行时崩溃。后面配置 VS2019 工程时,我会用条件宏分别指定,避免手动改。

3. 编写 .proto 文件并生成 C++ 类

3.1 .proto 文件的基础语法

消息定义是 protobuf 的核心,我们先用一个常用的“用户信息”例子感受一下 proto3 的语法:

syntax = "proto3"; package demo; message UserInfo { int32 id = 1; string name = 2; string email = 3; repeated string tags = 4; Address address = 5; message Address { string country = 1; string city = 2; string street = 3; } }

几个要点需要重点理解:

  • syntax = "proto3"必须写在第一行非注释位置,表示使用 proto3 语法。如果不写,protoc 默认用 proto2,字段的optional/required规则就会完全不一样,代码生成结果也会不同。

  • 每个字段后面都有一个数字编号(1、2、3...),这个编号是字段的二进制表示标识,一旦上线就不能乱改,否则会导致老数据反序列化失败。开发期可以调整,线上千万不要动。

  • repeated关键字表示数组/列表,在 C++ 里对应google::protobuf::RepeatedPtrField<std::string>

  • 消息可以嵌套,比如Address定义在UserInfo内部,生成的 C++ 类名会是双层命名,使用时写UserInfo::Address

3.2 使用 protoc 生成 C++ 代码

写完 .proto 后,打开命令行(或者直接在 VS2019 的开发者命令行里执行)跑 protoc:

E:\protobuf\install\bin\protoc.exe --cpp_out=./ user.proto

--cpp_out指定生成代码的输出目录。执行完后,当前目录多出两个文件:user.pb.huser.pb.cc。这两个文件就是 protobuf 根据我们的消息定义生成的 C++ 类实现。

我不建议直接在 .proto 所在目录生成代码并手工拷贝,因为工程里文件多起来之后容易乱。规范做法是:源码目录下建一个proto文件夹放 .proto,再建一个generated文件夹放生成代码,每次生成后直接覆盖,全程不手工改生成代码。

3.3 生成代码的核心组成

打开user.pb.h会发现里面的内容非常多,但核心其实就是:

  • class UserInfo : public ::google::protobuf::Message:最终要操作的消息类;
  • 字段的 getter/setter:比如name()set_name()
  • 序列化方法SerializeToString()/SerializeToArray()
  • 反序列化方法ParseFromString()/ParseFromArray()
  • 内存管理相关的arena接口,一般场景用不到;
  • 反射相关的 metadata 接口,调试时有点用。

你要知道的一点是:生成代码不要手动改,也基本不需要细读所有内容。把它当成一次性的“胶水代码”,使用的时候直接调用公共接口就行。

4. 在 VS2019 中配置 C++ 工程并跑通示例

4.1 创建工程与引入生成代码

打开 VS2019,新建一个 C++ 控制台应用项目,项目名我这里用ProtoDemo。工程建好之后,把user.pb.huser.pb.cc直接拖到“解决方案资源管理器”的“源文件”和“头文件”分类下,或者放在工程目录里再通过“添加现有项”引入。

这里有一个很多人不注意的点:user.pb.cc要参与编译,user.pb.h只是头文件声明。如果发现链接时找不到UserInfo类的符号,多半是.cc文件没有加进工程。

4.2 配置头文件路径与库路径

打开项目属性页(右键项目 -> 属性),重点看几个地方:

C/C++ -> 常规 -> 附加包含目录:添加E:\protobuf\install\include

链接器 -> 常规 -> 附加库目录:添加E:\protobuf\install\lib

链接器 -> 输入 -> 附加依赖项:这里要根据 Debug/Release 分别设置。

我自己的习惯是手动输入宏条件,不让它混在一起:

# Debug 版 $(SolutionDir)..\lib\libprotobufd.lib # Release 版 $(SolutionDir)..\lib\libprotobuf.lib

但在用 CMake 安装目录的时候,更直接的做法是在“附加依赖项”里两个都写进去,只要路径存在,链接器会自己选。不过为了规范和减小体积,建议还是不同配置填不同值。

还需要注意运行库的设置。VS2019 默认是/MD(Release)和/MDd(Debug),我们用 CMake 编 protobuf 时生成的库也默认是 MD 族,所以一般情况下不用动。如果你在公司项目里被强制要求用/MT静态运行库,那就需要去 CMake 里把 protobuf 也改成/MT重新编,否则链接阶段一定报错。

4.3 代码示例:完整的序列化与反序列化

配置完成之后,我们来写一个完整可运行的示例。这段代码覆盖了字段赋值、序列化、反序列化、嵌套消息和 repeated 字段的操作:

#include <iostream> #include <string> #include "user.pb.h" int main() { // 1. 构造消息对象并赋值 demo::UserInfo user; user.set_id(1001); user.set_name("zhangsan"); user.set_email("zhangsan@example.com"); // 2. 给 repeated 字段添加值 user.add_tags("student"); user.add_tags("cpp"); // 3. 设置嵌套消息 demo::UserInfo::Address* addr = user.mutable_address(); addr->set_country("China"); addr->set_city("Shanghai"); addr->set_street("Nanjing Road"); // 4. 序列化成字符串 std::string data; bool ok = user.SerializeToString(&data); if (!ok) { std::cerr << "serialize failed" << std::endl; return -1; } std::cout << "serialized bytes: " << data.size() << std::endl; // 5. 反序列化出新的对象 demo::UserInfo new_user; if (!new_user.ParseFromString(data)) { std::cerr << "parse failed" << std::endl; return -1; } // 6. 读取字段 std::cout << "id: " << new_user.id() << std::endl; std::cout << "name: " << new_user.name() << std::endl; std::cout << "email: " << new_user.email() << std::endl; // 遍历 repeated 字段 for (const auto& tag : new_user.tags()) { std::cout << "tag: " << tag << std::endl; } // 读取嵌套消息 if (new_user.has_address()) { const demo::UserInfo::Address& address = new_user.address(); std::cout << "country: " << address.country() << std::endl; std::cout << "city: " << address.city() << std::endl; std::cout << "street: " << address.street() << std::endl; } return 0; }

这段代码建议自己敲一遍,特别注意两点:

mutable_address()这个函数会先创建一个默认的Address对象再返回指针,所以可以直接给指针指向的对象赋值。网上有些老教程直接用set_allocated_address(),那个方法要手动管理内存,用不好容易出 double free,新手先用mutable_系列就行。

SerializeToString的性能问题:如果你在循环里高频调用,建议改成SerializeToArray(void* data, int size),提前分配一块缓冲区,减少 string 反复扩容的开销。这个优化在 QPS 高的服务里差别很明显。

5. 序列化与反序列化的进阶细节与性能优化

5.1 序列化结果分析

运行上面代码后,输出大致是:

serialized bytes: 42 id: 1001 name: zhangsan email: zhangsan@example.com tag: student tag: cpp country: China city: Shanghai street: Nanjing Road

42 字节比等价的 JSON 短很多,这就是 protobuf 压缩二进制格式的优势。它是用字段编号 + wire type 来区分数据的,字符串字段只存原始字节内容,不存键名。所以字段名随便改都不会影响线上数据解析,但字段编号改了就会出大问题,这也是我在 3.1 强调过的那句话。

5.2 性能优化:复用对象与动态分配

实际项目里如果每秒要处理几千条消息,创建和销毁 protobuf 对象的成本不可忽视。最直接的优化方法是复用对象:

demo::UserInfo user; for (int i = 0; i < 100000; ++i) { user.Clear(); user.set_id(i); user.set_name("test"); // 只序列化不反序列化,或者反过来 }

Clear()会重置对象的逻辑状态,但底层的字符串缓冲区、RepeatedPtrField 的存储空间会被复用,避免反复 malloc/free。这一点在服务端代码里很常见,对性能敏感的朋友可以重点关注。

此外,如果你同时处理大量小消息,可以考虑Arena机制。简单理解就是一次性分配一块大内存,消息对象都在里面申请空间,统一释放。但 Arena 会让代码可读性下降,小项目不用刻意引入。

5.3 字节序与平台兼容性

protobuf 序列化后的数据自带格式描述,整型都是用 little-endian 存储的,所以跨平台传输时不需要自己做字节序转换。这一点比手动拼二进制流方便太多。我自己以前做网络协议时用htonl转来转去,稍不留神就出错,换成 protobuf 后这类问题直接消失。

6. 常见问题排查与避坑实录

这个章节直接列我在实际使用过程中遇到的高频问题,每条都是踩过坑换来的。

6.1 protoc 不是内部或外部命令

如果直接在 cmd 里敲 protoc 提示找不到命令,是因为没把 protoc.exe 所在目录加进系统 PATH。两种解法:临时用绝对路径执行,或者在系统环境变量 Path 中添加E:\protobuf\install\bin。我推荐后者,因为后续写批处理脚本、嵌入 CMake 时都会方便很多。

6.2 链接错误 LNK2019 / LNK2001

报错信息通常长这样:

LNK2019 unresolved external symbol "public: std::string ...

常见原因有四个:

  • 工程没有包含user.pb.cc源文件;
  • 附加依赖项没写libprotobuf.liblibprotobufd.lib
  • 工程是 x64,编译的库是 Win32(或反过来);
  • Debug/Release 混链了 protobuf 库。

这些从项目属性和构建日志里基本能定位。建议把构建输出切到“详细”级别,看实际链接了哪些库、在找哪些符号。

6.3 运行时崩溃或数据解析失败

程序能编过但运行崩,常见是这些情况:

  • 反序列化时用了不匹配的 .proto 版本(比如加了新字段但编号和老数据冲突);
  • SerializeToString之后的结果用二进制方式算长度、拷贝到 char* 时没考虑二进制数据中间有\0
  • 多线程里共享同一个消息对象,没有加锁。

最后一个在多线程环境下特别容易踩:多个线程同时调用SerializeToString或者同时修改字段,内部状态就乱了。解决办法是每个线程持有独立的消息对象,或者加互斥锁。

6.4 中文乱码

protobuf 的 string 类型存储的是 UTF-8 编码字节。如果你从 GBK 编码的文本文件或 Windows 控制台直接读入中文字符串再 set 进去,反序列化出来打印时会乱码。

建议工程内统一使用 UTF-8 for 代码文件,读取外部文件时做编码转换。Windows 控制台默认代码页如果是 936(GBK),打印 UTF-8 字符串也可能乱码,可以直接在 main 函数开头执行:

system("chcp 65001");

或者在 VS2019 里勾选项目属性的“字符集”相关配置,让控制台切换到 UTF-8 模式。

6.5 VS2019 编译提示“无法打开包括文件 google/protobuf/message.h”

这种情况基本都是附加包含目录路径不对。检查一下E:\protobuf\install\include下是否存在google\protobuf\message.h这个文件。如果不存在,说明 protobuf 安装路径不完整,重新执行cmake --install .大概率能解决。

7. 从单体示例扩展到真实项目的建议

到这里,一个完整的 protobuf + VS2019 C++ 示例已经跑通了。不过建议你再往下想一步:真实项目里不会只有一个 .proto 文件,也不会只有一对消息。如何管理 .proto 、生成代码和工程依赖,往往比调通当前 demo 更重要。

我的实践做法是这样的:仓库根目录维护proto文件夹,按业务模块拆分子目录,例如proto/userproto/order;用一个批处理脚本统一调用 protoc 生成 C++ 代码到generated目录;生成代码不提交到 Git 仓库(或者提交但要在 README 里注明生成命令),通过构建脚本自动生成。这样多人协作时,.proto 变更和生成代码变更就不容易出现“别人没跑 protoc 导致代码不一致”的问题。

另一个建议是版本管理。即使你当前项目用的是 3.8.0,也要在文档里记录你用的 protoc 版本,因为不同小版本的 protoc 生成的代码可能略有差别。以后升级 protobuf 大版本时,别忘了重新生成一遍所有 .pb.cc/.pb.h,别继续沿用旧文件,否则很容易出现隐性的数据兼容问题。

最后,如果你要把 protobuf 用在 UDP 这种面向报文的场景,建议自己封装一层消息边界和长度字段,不要指望 protobuf 自带流式分包能力。每次发送前先序列化为字节数组,再拼上一个固定长度的头部(比如 4 字节大端长度),接收端先收头部再按长度读消息体。这个封装我在多个项目中都重复实现过,没有翻过车。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/2 19:56:40

用Grok Bot和Notion在手机上10分钟整理关注列表

实际使用 Notion 管理信息时&#xff0c;最麻烦的往往不是写字&#xff0c;而是把散落在不同 App 里的关注列表整理成结构化数据。Grok Bot 这类带对话和数据处理能力的工具出现后&#xff0c;这个流程可以明显简化&#xff1a;直接用手机对话&#xff0c;让 AI 把账号、标题、…

作者头像 李华
网站建设 2026/9/2 19:52:35

OrCAD Capture 17.2补丁包实战指南:版本解析、安装联动与排错

简介&#xff1a;OrCAD Capture 17.2是Cadence公司推出的专业原理图设计软件&#xff0c;面向电子硬件工程师、PCB Layout人员以及高校电子类专业师生&#xff0c;用于完成从电路原理图绘制、元器件符号管理到网表输出的完整前端设计流程。这份资源以7z压缩包形式提供&#xff…

作者头像 李华
网站建设 2026/9/2 19:51:48

Claude Code自我验收闭环:五个底层习惯让Agent从玩具变生产工具

最近在梳理 Claude Code 的落地流程时&#xff0c;我翻到一条公开分享&#xff1a;Claude Code 团队负责人 Boris 提到&#xff0c;团队内部最看重的不是“能写多少代码”&#xff0c;而是“如何验证代码真的写对了”。这句话看起来像一句正确的废话&#xff0c;但如果你真正在…

作者头像 李华
网站建设 2026/9/2 19:51:12

MATLAB下LS-SVM工具箱实战:原理、参数调优与应用

简介&#xff1a;Matlab LS-SVMlab1.5 工具箱是一套面向科研与工程实践的最小二乘支持向量机算法实现&#xff0c;适合需要处理非线性分类与回归、希望快速搭建SVM模型的机器学习研究人员和工程师。压缩包内共81个文件&#xff0c;以64个m源文件为主&#xff0c;覆盖模型训练、…

作者头像 李华
网站建设 2026/9/2 19:51:01

UNK20现场技术拆解:Trance DJ Set编排与执行链路

这次我们来看的不是某个开源模型&#xff0c;而是一场演出的技术观察&#xff1a;UNK20 Day1 的 VII Simon Patterson。VII 既是罗马数字 7&#xff0c;也是 Simon Patterson 的核心厂牌标识。把它当成一个“现场演出项目”来拆&#xff0c;值得关注的不是歌单&#xff0c;而是…

作者头像 李华