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]通过一个对象 ...
随机推荐
- 1、opencv3.3.0和cmake安装步骤(按照以下步骤安装后仅能在PC上运行,动态库也是PC端的属性)
1.下载安装CMake for Linux 下载地址:https://cmake.org/download/ 我下载的版本是,下载文件cmake-3.11.1.tar.gz ./bootstrap m ...
- 三国武将查询系统 //Java 訪问 数据库
import java.awt.*; import javax.swing.*; import java.awt.event.ActionListener; import java.awt.event ...
- VS2012调试C++工程DLL
1.C++工程属性对话框 2.配置属性: (1)常规:输出目录:..\Bin\WFCrawler(调用DLL的工程) 中间目录:..\Bin\WFCrawler(调用DLL的工程 ...
- ps树叶的雕刻
1.学习了图层建立,通道复制,通道载如.图层复制粘贴透明,色阶改动(ctrl+L),蒙板建立(必须不在背景上),蒙板刻图.蒙板画画(白色,黑色),蒙板填充(ctrl+backspace),(atrl+ ...
- HTML标签的权重
原文 简书原文:https://www.jianshu.com/p/c86d540aec40 大纲 1.HTML权重标签 2.常见的权重标签 3.常见的权重标签的使用 1.HTML权重标签 SEOer ...
- 常用有效检测数据库运行状态SQL脚本
1.查看数据库中不为 InnoDB 引擎的表 SELECT TABLE_SCHEMA, TABLE_NAME, ENGINE FROM information_schema.TABLES W ...
- 我眼中的c++编程总结-20150602
断断续续的学习了非常多东西,有51.Avr.ARM.PLC.C\C++.C#.TB.MC.mql4.linux....等等,近乎填鸭或者囫囵吞枣的.甚至饿狼般的扑到里面,慢慢的积累和理解中,非常多知识 ...
- 用JavaScript和CSS实现“在页面中水平和垂直居中”的时钟
思路:实现起来最麻烦的事实上是水平居中和垂直居中,当中垂直居中是最麻烦的. 考虑到浏览器兼容性,网上看了一些资料,发如今页面中垂直居中确实没有什么太好的办法. 于是就採用了position:fixed ...
- ios开发之手势处理 之手势识别一
#import "ViewController.h" @interface ViewController ()<UIGestureRecognizerDelegate> ...
- hdu 1052 田忌赛马
贪心,排序从大到小.. 先比大的.跑只是就拿最小的来送死.. , 假设是平局就比后面的... 若后面也是平局就拿去跟前面的去跑. .. #include<stdio.h> #include ...