c++如何编译和使用probuf
关于protobuf的介绍,请阅读文章关于protobuf的介绍和优缺点,这里不做过多介绍,直接开始使用教程。
编译protobuf
1:安装cmake
cmake用于生成protobuf的VisualStudio的工程,需要通过VisualStudio编译生成libprotobuf.lib和protoc.exe文件。
2:下载protobuf代码
打开https://github.com/protocolbuffers/protobuf/releases/tag/v21.5,c++使用选择下载文件 protobuf-cpp-xxx.tar.gz 或 protobuf-cpp-xxx.zip文件。
下载成功后解压文件。
3:通过cmake生成vs工程
打开cmake,填入protobuf目录下的cmake文件夹到Where is the source code编辑框
随便填入一个生成目录到Where to build the binaries编辑框,比如protobuf目录下的build文件夹
点击Configure,选择生成需要的vs版本,点击确定
Configure完成后点击Generate生成vs工程
打开生成目录,找到protobuf.sln文件,打开并编译 libprotobuf-lite(l ibprotobuf-lite 没有描述符和反射等某些功能,但体积会小一个数量级,如果需要可以选择编译libprotobuf) 和 protoc 工程,会生成 libprotobuf-lite.lib(debug模式会在文件名末尾加d) 和 protoc.exe文件
配置工程依赖
1:复制文件所需文件到工程对应目录下
把 protobuf/src/ 目录下的 google 文件夹拷贝到工程目录下
把 protobuf输出目录下的 libprotobuf-lite.lib 和 protoc.exe 分 Debug 和 Release环境分别拷贝到工程的输出目录下
2:配置附加包含目录和附加库目录
打开工程解决方案,右击工程选择属性,C/C++ -> 常规 -> 附加包含目录,填入当前目录 ./
右击工程选择属性,连接器 -> 常规 -> 附加库目录,填入 libproto-lite.lib 所在目录, 我选择的是输出目录,可以使用变量 $(OutDir)
3:依赖静态库
可以选择在 链接器 -> 输入 -> 附加依赖项 中填入 libproto-lite.lib文件,也可以选择在代码中链接,看个人习惯,我喜欢在代码中链接
创建links.cpp文件并打开
分 Debug 和 Release 分别链接 libprotobuf,代码如下:
#ifdef _DEBUG #pragma comment(lib, "libprotobuf-lited.lib") #else #pragma comment(lib, "libprotobuf-lite.lib") #endif // DEBUG
proto文件的编写和配置
1:创建.proto文件
首先在工程目录下创建一个文件夹用于专门存储.proto文件,比如我创建的是 protobuf 文件夹
打开文件夹,创建一个后缀为 .proto 的文件,比如 message.proto
2:编写.proto文件
打开 message.proto 文件,配置需要的字段,比如:
syntax = "proto3"; package tutorial; option optimize_for = LITE_RUNTIME; message Person { optional string name = 1; optional int32 id = 2; optional string email = 3; enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } message PhoneNumber { optional string number = 1; optional PhoneType type = 2; } repeated PhoneNumber phones = 4; }
每个字段都必须使用optional,repeated,required标签修饰,注意:proto3已经不支持required标签,并且proto3不支持设置默认值。
optional
: 该字段可以设置也可以不设置。如果未设置可选字段值,则使用默认值。对于简单类型,您可以指定自己的默认值,就像我们在示例中为电话号码所做的那样type
。否则,使用系统默认值:数字类型为零,字符串为空字符串,布尔值为 false。对于嵌入式消息,默认值始终是消息的“默认实例”或“原型”,没有设置任何字段。调用访问器以获取未显式设置的可选(或必需)字段的值始终返回该字段的默认值。repeated
:该字段可以重复任意次数(包括零次)。重复值的顺序将保存在协议缓冲区中。将重复字段视为动态大小的数组。required
:必须提供该字段的值,否则该消息将被视为“未初始化”。如果libprotobuf
在调试模式下编译,序列化未初始化的消息将导致断言失败。在优化的构建中,会跳过检查并且无论如何都会写入消息。但是,解析未初始化的消息总是会失败(通过false
从 parse 方法返回)。除此之外,必填字段的行为与可选字段完全相同。
syntax 为显示的指定proto的版本。
package 指定包名,避免与其它 .proto文件的字段冲突,在C++中,package其实就是生成了一个名字控件(namespace)。
message 会生成一个类,类里面包含的就是每个字段的变量和一些操作方法。
每个字段后面都必须有一个字段编号“=数字;”它可以不是递增的,但每个message作用域内它必须是唯一的。
如果链接的是 libproto-lite.lib 库,那么必须在 .proto 文件开头加上 “option optimize_for = LITE_RUNTIME”;
关于required被在proto3移除的原因大概是因为:
您应该非常小心将字段标记为required
。如果在某些时候您希望停止编写或发送必填字段,则将该字段更改为可选字段将会有问题——老读者会认为没有此字段的消息不完整,可能会无意中拒绝或丢弃它们。您应该考虑为您的缓冲区编写特定于应用程序的自定义验证例程。在 Google 内部,required
字段是非常不受欢迎的;proto2 语法中定义的大多数消息optional
仅使用repeated
。(Proto3 根本不支持required
字段。)
更多关于protobuf的详细介绍可以参略谷歌文档:https://developers.google.com/protocol-buffers?hl=cn
3:配置.proto文件自定义生成
右击工程,选择 添加 -> 新建筛选器,创建一个筛选器,比如protobuf
右击创建的筛选器,添加 -> 现有项,找到刚才创建的 message.proto 文件,选择添加即可
右击 message.pro 文件,配置属性 -> 常规 -> 项类型,选择 “自定义生成工具”
右击 message.pro 文件,配置属性 -> 自定义生成工具 -> 命令行,填入 “$(OutDir)protoc.exe --proto_path=$(ProjectDir)protobuf --cpp_out=$(ProjectDir) %(FullPath)”
这是使用protoc.exe编译 message.proto 文件,生成 message.pb.cc 和 message.pb.h 文件,这里填入的就是执行protoc.exe的命令行
protoc --proto_path=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/xxx.proto
$SRC_DIR是.proto所在的目录,$DST_DIR是需要输出的目录
右击 message.pro 文件,配置属性 -> 自定义生成工具 -> 输出,填入 “$(ProjectDir)%(Filename).pb.cc;$(ProjectDir)%(Filename).pb.h;%(Outputs)”
protoc.exe即将生成的.cc和.h文件配置到输出,否则protoc.exe无法正常运行并生成所需的文件。
demo代码
#include <string> #include "message.pb.h" int main() { GOOGLE_PROTOBUF_VERIFY_VERSION; tutorial::Person john; john.set_name("hello word"); john.set_id(998); tutorial::Person_PhoneNumber* ph1 = john.add_phones(); ph1->set_number("111111"); ph1->set_type(tutorial::Person_PhoneType_HOME); tutorial::Person_PhoneNumber* ph2 = john.add_phones(); ph2->set_number("222222"); ph2->set_type(tutorial::Person_PhoneType_MOBILE); std::string serialize; bool ret = john.SerializePartialToString(&serialize); tutorial::Person john2; ret = john2.ParseFromString(serialize); std::string name = john2.name(); int32_t id = john2.id(); for (auto& iter : john2.phones()) { std::string phone = iter.number(); tutorial::Person_PhoneType type = iter.type(); int a = 1; } std::string debug = john.DebugString(); google::protobuf::ShutdownProtobufLibrary(); }