protobuf反射详解
本文主要介绍protobuf里的反射功能,使用的pb版本为2.6.1,同时为了简洁,对repeated/extension字段的处理方法没有说明。
最初是起源于这样一个问题:
给定一个pb对象,如何自动遍历该对象的所有字段?
即是否有一个通用的方法可以遍历任意pb对象的所有字段,而不用关心具体对象类型。
使用场景上有很多:
比如json格式字符串的相互转换,或者bigtable里根据pb对象的字段自动写列名和对应的value。
例如定义了pb messge类型Person如下:
Person person;person.set_name("yingshin");person.set_age(21);
能否将该对象自动转化为json字符串{"name":"yingshin","age":21},或者自动的写hbase里的多列:
| key | column-name | column-value |
|---|---|---|
| uid | name | yingshin |
| uid | age | 21 |
如果设置了新的字段,比如person.set_email("izualzhy@163.com"),则自动添加新的一列:
| key | column-name | column-value |
|---|---|---|
| uid | izualzhy@163.com |
答案就是 pb的反射功能。
我们的目标是提供这样两个接口:
//从给定的message对象序列化为固定格式的字符串void serialize_message(const google::protobuf::Message& message, std::string* serialized_string);//从给定的字符串按照固定格式还原为原message对象void parse_message(const std::string& serialized_string, google::protobuf::Message* message);
接下来逐步介绍下如何实现。
1. 反射相关接口
要介绍pb的反射功能,先看一个相关的UML示例图:

各个类以及接口说明:
1.1 Message
Person是自定义的pb类型,继承自Message. MessageLite作为Message基类,更加轻量级一些。
通过Message的两个接口GetDescriptor/GetReflection,可以获取该类型对应的Descriptor/Reflection。
1.2 Descriptor
Descriptor是对message类型定义的描述,包括message的名字、所有字段的描述、原始的proto文件内容等。
本文中我们主要关注跟字段描述相关的接口,例如:
- 获取所有字段的个数:
int field_count() const - 获取单个字段描述类型
FieldDescriptor的接口有很多个,例如
const FieldDescriptor* field(int index) const;//根据定义顺序索引获取const FieldDescriptor* FindFieldByNumber(int number) const;//根据tag值获取const FieldDescriptor* FindFieldByName(const string& name) const;//根据field name获取
1.3 FieldDescriptor
FieldDescriptor描述message中的单个字段,例如字段名,字段属性(optional/required/repeated)等。
对于proto定义里的每种类型,都有一种对应的C++类型,例如:
enum CppType {CPPTYPE_INT32 = 1, //TYPE_INT32, TYPE_SINT32, TYPE_SFIXED32}
获取类型的label属性:
enum Label {LABEL_OPTIONAL = 1, //optionalLABEL_REQUIRED = 2, //requiredLABEL_REPEATED = 3, //repeatedMAX_LABEL = 3, //Constant useful for defining lookup tables indexed by Label.}
获取字段的名称:const string& name() const;。
1.4 Reflection
Reflection主要提供了动态读写pb字段的接口,对pb对象的自动读写主要通过该类完成。
对每种类型,Reflection都提供了一个单独的接口用于读写字段对应的值。
例如对于读操作:
virtual int32 GetInt32 (const Message& message,const FieldDescriptor* field) const = 0;virtual int64 GetInt64 (const Message& message,const FieldDescriptor* field) const = 0;
特殊的,对于枚举和嵌套的message:
virtual const EnumValueDescriptor* GetEnum(const Message& message, const FieldDescriptor* field) const = 0;// See MutableMessage() for the meaning of the "factory" parameter.virtual const Message& GetMessage(const Message& message,const FieldDescriptor* field,MessageFactory* factory = NULL) const = 0;
对于写操作也是类似的接口,例如SetInt32/SetInt64/SetEnum等。
2. 反射示例
示例主要是接收任意类型的message对象,遍历解析其中的每个字段、以及对应的值,按照自定义的格式存储到一个string中。同时重新反序列化该string,读取字段以及value,填充到message对象中。例如:
其中Person是自定义的protobuf message类型,用于设置一些字段验证我们的程序。
单纯的序列化/反序列化功能可以通过pb自带的SerializeToString/ParseFromString接口完成。这里主要是为了同时展示自动从pb对象里提取field/value,自动根据field/value来还原pb对象这个功能。
int main() {std::string serialized_string;{tutorial::Person person;person.set_name("yingshin");person.set_id(123456789);person.set_email("zhy198606@gmail.com");::tutorial::Person_PhoneNumber* phone = person.mutable_phone();phone->set_type(tutorial::Person::WORK);phone->set_number("13266666666");serialize_message(person, &serialized_string);}{tutorial::Person person;parse_message(serialized_string, &person);}return 0;}
其中Person定义是对example里的addressbook.proto做了少许修改(修改的原因是本文没有涉及pb里数组的处理)
package tutorial;message Person {required string name = 1;required int32 id = 2; // Unique ID number for this person.optional string email = 3;enum PhoneType {MOBILE = 0;HOME = 1;WORK = 2;}message PhoneNumber {required string number = 1;optional PhoneType type = 2 [default = HOME];}optional PhoneNumber phone = 4;}
3. 反射实例实现
3.1 serialize_message
serialize_message遍历提取message中各个字段以及对应的值,序列化到string中。
主要思路就是通过Descriptor得到每个字段的描述符:字段名、字段的cpp类型。
通过Reflection的GetX接口获取对应的value。
void serialize_message(const google::protobuf::Message& message, std::string* serialized_string) {const google::protobuf::Descriptor* descriptor = message.GetDescriptor();const google::protobuf::Reflection* reflection = message.GetReflection();for (int i = 0; i < descriptor->field_count(); ++i) {const google::protobuf::FieldDescriptor* field = descriptor->field(i);bool has_field = reflection->HasField(message, field);if (has_field) {//arrays not supportedassert(!field->is_repeated());switch (field->cpp_type()) {#define CASE_FIELD_TYPE(cpptype, method, valuetype)\case google::protobuf::FieldDescriptor::CPPTYPE_##cpptype:{\valuetype value = reflection->Get##method(message, field);\int wsize = field->name().size();\serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));\serialized_string->append(field->name().c_str(), field->name().size());\wsize = sizeof(value);\serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));\serialized_string->append(reinterpret_cast<char*>(&value), sizeof(value));\break;\}CASE_FIELD_TYPE(INT32, Int32, int);CASE_FIELD_TYPE(UINT32, UInt32, uint32_t);CASE_FIELD_TYPE(FLOAT, Float, float);CASE_FIELD_TYPE(DOUBLE, Double, double);CASE_FIELD_TYPE(BOOL, Bool, bool);CASE_FIELD_TYPE(INT64, Int64, int64_t);CASE_FIELD_TYPE(UINT64, UInt64, uint64_t);#undef CASE_FIELD_TYPEcase google::protobuf::FieldDescriptor::CPPTYPE_ENUM: {int value = reflection->GetEnum(message, field)->number();int wsize = field->name().size();//写入name占用字节数serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));//写入nameserialized_string->append(field->name().c_str(), field->name().size());wsize = sizeof(value);//写入value占用字节数serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));//写入valueserialized_string->append(reinterpret_cast<char*>(&value), sizeof(value));break;}case google::protobuf::FieldDescriptor::CPPTYPE_STRING: {std::string value = reflection->GetString(message, field);int wsize = field->name().size();serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));serialized_string->append(field->name().c_str(), field->name().size());wsize = value.size();serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));serialized_string->append(value.c_str(), value.size());break;}case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: {std::string value;int wsize = field->name().size();serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));serialized_string->append(field->name().c_str(), field->name().size());const google::protobuf::Message& submessage = reflection->GetMessage(message, field);serialize_message(submessage, &value);wsize = value.size();serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));serialized_string->append(value.c_str(), value.size());break;}}}}}
3.2 parse_message
parse_message通过读取field/value,还原message对象。
主要思路跟serialize_message很像,通过Descriptor得到每个字段的描述符FieldDescriptor,通过Reflection的SetX填充message。
void parse_message(const std::string& serialized_string, google::protobuf::Message* message) {const google::protobuf::Descriptor* descriptor = message->GetDescriptor();const google::protobuf::Reflection* reflection = message->GetReflection();std::map<std::string, const google::protobuf::FieldDescriptor*> field_map;for (int i = 0; i < descriptor->field_count(); ++i) {const google::protobuf::FieldDescriptor* field = descriptor->field(i);field_map[field->name()] = field;}const google::protobuf::FieldDescriptor* field = NULL;size_t pos = 0;while (pos < serialized_string.size()) {int name_size = *(reinterpret_cast<const int*>(serialized_string.substr(pos, sizeof(int)).c_str()));pos += sizeof(int);std::string name = serialized_string.substr(pos, name_size);pos += name_size;int value_size = *(reinterpret_cast<const int*>(serialized_string.substr(pos, sizeof(int)).c_str()));pos += sizeof(int);std::string value = serialized_string.substr(pos, value_size);pos += value_size;std::map<std::string, const google::protobuf::FieldDescriptor*>::iterator iter =field_map.find(name);if (iter == field_map.end()) {fprintf(stderr, "no field found.\n");continue;} else {field = iter->second;}assert(!field->is_repeated());switch (field->cpp_type()) {#define CASE_FIELD_TYPE(cpptype, method, valuetype)\case google::protobuf::FieldDescriptor::CPPTYPE_##cpptype: {\reflection->Set##method(\message,\field,\*(reinterpret_cast<const valuetype*>(value.c_str())));\std::cout << field->name() << "\t" << *(reinterpret_cast<const valuetype*>(value.c_str())) << std::endl;\break;\}CASE_FIELD_TYPE(INT32, Int32, int);CASE_FIELD_TYPE(UINT32, UInt32, uint32_t);CASE_FIELD_TYPE(FLOAT, Float, float);CASE_FIELD_TYPE(DOUBLE, Double, double);CASE_FIELD_TYPE(BOOL, Bool, bool);CASE_FIELD_TYPE(INT64, Int64, int64_t);CASE_FIELD_TYPE(UINT64, UInt64, uint64_t);#undef CASE_FIELD_TYPEcase google::protobuf::FieldDescriptor::CPPTYPE_ENUM: {const google::protobuf::EnumValueDescriptor* enum_value_descriptor =field->enum_type()->FindValueByNumber(*(reinterpret_cast<const int*>(value.c_str())));reflection->SetEnum(message, field, enum_value_descriptor);std::cout << field->name() << "\t" << *(reinterpret_cast<const int*>(value.c_str())) << std::endl;break;}case google::protobuf::FieldDescriptor::CPPTYPE_STRING: {reflection->SetString(message, field, value);std::cout << field->name() << "\t" << value << std::endl;break;}case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: {google::protobuf::Message* submessage = reflection->MutableMessage(message, field);parse_message(value, submessage);break;}default: {break;}}}}
protobuf反射详解的更多相关文章
- C#反射の反射详解
C#反射の反射详解(点击跳转)C#反射の反射接口(点击跳转)C#反射反射泛型接口(点击跳转)C#反射の一个泛型反射实现的网络请求框架(点击跳转) 一.什么是反射 反射(Reflection):这是.N ...
- java 反射详解
反射的概念和原理 类字节码文件是在硬盘上存储的,是一个个的.class文件.我们在new一个对象时,JVM会先把字节码文件的信息读出来放到内存中,第二次用时,就不用在加载了,而是直接使用之前缓存的这个 ...
- Java 反射详解 转载
java 反射 定义 功能 示例 概要: Java反射机制详解 | |目录 1反射机制是什么 2反射机制能做什么 3反射机制的相关API ·通过一个对象获得完整的包名和类名 ·实例化Class类对象 ...
- ProtoBuf格式详解
- 数据结构 通过前面的例子,可以看到PB的数据结构就是每项数据独立编码,包含一个表示数据类型 - Varint Varint是一种对数字进行编码的方法,将数字编码成不定长的二进制数据,数值越小,编码 ...
- java反射 详解!!!!
java反射(特别通俗易懂) 反射是框架设计的灵魂 (使用的前提条件:必须先得到代表的字节码的Class,Class类用于表示.class文件(字节码)) 一.反射的概述 JAVA反射机制是在运行状态 ...
- protobuf使用详解
https://blog.csdn.net/skh2015java/article/details/78404235 原文地址:http://blog.csdn.net/lyjshen/article ...
- 【原创】go语言学习(十八)反射详解
目录 变量介绍 反射介绍 结构体反射 反射总结以及应用场景 变量介绍 1.变量的内在机制 A. 类型信息,这部分是元信息,是预先定义好的B. 值类型,这部分是程序运行过程中,动态改变的 var arr ...
- java反射详解
本篇文章依旧采用小例子来说明,因为我始终觉的,案例驱动是最好的,要不然只看理论的话,看了也不懂,不过建议大家在看完文章之后,在回过头去看看理论,会有更好的理解. 下面开始正文. [案例1]通过一个对象 ...
- java反射详解(转)
本篇文章依旧采用小例子来说明,因为我始终觉的,案例驱动是最好的,要不然只看理论的话,看了也不懂,不过建议大家在看完文章之后,在回过头去看看理论,会有更好的理解. 下面开始正文. [案例1]通过一个对象 ...
随机推荐
- PB导出数据excel格式dw2xls
PB导出数据excel格式dw2xls 使用DW2XLS控件 语法 uf_save_dw_as_excel ( dw, filename ) 參数 dw A reference to the data ...
- Visual Studio中你所不知道的智能感知
在Visual Studio中的智能感知,相信大家都用过.summary,param,returns这几个相信很多人都用过的吧.那么field,value等等这些呢. 首先在Visual Studio ...
- css3-12 transition+css或transform实现过渡动画
css3-12 transition+css或transform实现过渡动画 一.总结 一句话总结:首先要设置hover后的效果,然后在transition里面指定执行哪些样式和执行时间为多长. 1. ...
- [WPF自定义控件库]排序、筛选以及高亮
1. 如何让列表的内容更容易查找 假设有这么一个列表(数据源在本地),由于内容太多,要查找到其中某个想要的数据会比较困难.要优化这个列表,无非就是排序.筛选和高亮. 改造过的结果如上. 2. 排序 在 ...
- stm32四种输入
1. 上拉输入(GPIO_Mode_IPU) 上拉输入就是信号进入芯片后加了一个上拉电阻,再经过施密特触发器转换成0.1信号,读取此时的引脚电平为高电平: 2. ...
- Fragment之一:Fragment入门 分类: H1_ANDROID 2013-11-15 18:16 2799人阅读 评论(2) 收藏
参考自张泽华视频 Fragment是自Android3.0后引入的特性,主要用于在不同的屏幕尺寸中展现不同的内容. Fragment必须被嵌入Activity中使用,总是作为Activity的组成部分 ...
- ios app初始化和数据迁移的设计思路
整体思路 一般app启动之后,都有一个初始化的过程. 此外兴许app升级,还须要考虑数据迁移.所以初始化和数据迁移的框架.在初期的版本号就要考虑好 总结一下我们的app採取的方案: 1.在持久化的文件 ...
- 授人玫瑰 手留余香 --纪念python3.2.3官方文档翻译结束
当你点击看到这篇文章的时候.你已经得到了祝福. 一个来自夜深人静的码农,在2014年5月19号的01:18分.默默为你献上祝福. 希望你.我和他,每个在IT行业中奋斗的人.能找到属于自己一片天空. 在 ...
- 基于 Android NDK 的学习之旅----- C调用Java
许多成熟的C引擎要移植到Android 平台上使用 , 一般都会 提供 一些接口, 让Android sdk 和 jdk 实现. 下文将会介绍 C 如何 通过 JNI 层调用 Java 的静态和非静态 ...
- Android 仿微信朋友圈点击图片变暗
package cn.eoe.leigo.view; import android.content.Context; import android.graphics.Bitmap; import an ...