本文主要介绍protobuf里的反射功能,使用的pb版本为2.6.1,同时为了简洁,对repeated/extension字段的处理方法没有说明。

最初是起源于这样一个问题:

给定一个pb对象,如何自动遍历该对象的所有字段?

即是否有一个通用的方法可以遍历任意pb对象的所有字段,而不用关心具体对象类型。 

使用场景上有很多:

比如json格式字符串的相互转换,或者bigtable里根据pb对象的字段自动写列名和对应的value。

例如定义了pb messge类型Person如下:

  1. Person person;
  2. person.set_name("yingshin");
  3. 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 email izualzhy@163.com

答案就是 pb的反射功能。

我们的目标是提供这样两个接口:

  1. //从给定的message对象序列化为固定格式的字符串
  2. void serialize_message(const google::protobuf::Message& message, std::string* serialized_string);
  3. //从给定的字符串按照固定格式还原为原message对象
  4. 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文件内容等。

本文中我们主要关注跟字段描述相关的接口,例如:

  1. 获取所有字段的个数:int field_count() const
  2. 获取单个字段描述类型FieldDescriptor的接口有很多个,例如
  1. const FieldDescriptor* field(int index) const;//根据定义顺序索引获取
  2. const FieldDescriptor* FindFieldByNumber(int number) const;//根据tag值获取
  3. const FieldDescriptor* FindFieldByName(const string& name) const;//根据field name获取
1.3 FieldDescriptor

FieldDescriptor描述message中的单个字段,例如字段名,字段属性(optional/required/repeated)等。

对于proto定义里的每种类型,都有一种对应的C++类型,例如:

  1. enum CppType {
  2. CPPTYPE_INT32 = 1, //TYPE_INT32, TYPE_SINT32, TYPE_SFIXED32
  3. }

获取类型的label属性:

  1. enum Label {
  2. LABEL_OPTIONAL = 1, //optional
  3. LABEL_REQUIRED = 2, //required
  4. LABEL_REPEATED = 3, //repeated
  5. MAX_LABEL = 3, //Constant useful for defining lookup tables indexed by Label.
  6. }

获取字段的名称:const string& name() const;

1.4 Reflection

Reflection主要提供了动态读写pb字段的接口,对pb对象的自动读写主要通过该类完成。

对每种类型,Reflection都提供了一个单独的接口用于读写字段对应的值。

例如对于读操作:

  1. virtual int32 GetInt32 (const Message& message,
  2. const FieldDescriptor* field) const = 0;
  3. virtual int64 GetInt64 (const Message& message,
  4. const FieldDescriptor* field) const = 0;

特殊的,对于枚举和嵌套的message:

  1. virtual const EnumValueDescriptor* GetEnum(
  2. const Message& message, const FieldDescriptor* field) const = 0;
  3. // See MutableMessage() for the meaning of the "factory" parameter.
  4. virtual const Message& GetMessage(const Message& message,
  5. const FieldDescriptor* field,
  6. 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对象这个功能。

  1. int main() {
  2. std::string serialized_string;
  3. {
  4. tutorial::Person person;
  5. person.set_name("yingshin");
  6. person.set_id(123456789);
  7. person.set_email("zhy198606@gmail.com");
  8. ::tutorial::Person_PhoneNumber* phone = person.mutable_phone();
  9. phone->set_type(tutorial::Person::WORK);
  10. phone->set_number("13266666666");
  11. serialize_message(person, &serialized_string);
  12. }
  13. {
  14. tutorial::Person person;
  15. parse_message(serialized_string, &person);
  16. }
  17. return 0;
  18. }

其中Person定义是对example里的addressbook.proto做了少许修改(修改的原因是本文没有涉及pb里数组的处理)

  1. package tutorial;
  2. message Person {
  3. required string name = 1;
  4. required int32 id = 2; // Unique ID number for this person.
  5. optional string email = 3;
  6. enum PhoneType {
  7. MOBILE = 0;
  8. HOME = 1;
  9. WORK = 2;
  10. }
  11. message PhoneNumber {
  12. required string number = 1;
  13. optional PhoneType type = 2 [default = HOME];
  14. }
  15. optional PhoneNumber phone = 4;
  16. }

3. 反射实例实现

3.1 serialize_message

serialize_message遍历提取message中各个字段以及对应的值,序列化到string中。

主要思路就是通过Descriptor得到每个字段的描述符:字段名、字段的cpp类型。

通过Reflection的GetX接口获取对应的value。

  1. void serialize_message(const google::protobuf::Message& message, std::string* serialized_string) {
  2. const google::protobuf::Descriptor* descriptor = message.GetDescriptor();
  3. const google::protobuf::Reflection* reflection = message.GetReflection();
  4. for (int i = 0; i < descriptor->field_count(); ++i) {
  5. const google::protobuf::FieldDescriptor* field = descriptor->field(i);
  6. bool has_field = reflection->HasField(message, field);
  7. if (has_field) {
  8. //arrays not supported
  9. assert(!field->is_repeated());
  10. switch (field->cpp_type()) {
  11. #define CASE_FIELD_TYPE(cpptype, method, valuetype)\
  12. case google::protobuf::FieldDescriptor::CPPTYPE_##cpptype:{\
  13. valuetype value = reflection->Get##method(message, field);\
  14. int wsize = field->name().size();\
  15. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));\
  16. serialized_string->append(field->name().c_str(), field->name().size());\
  17. wsize = sizeof(value);\
  18. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));\
  19. serialized_string->append(reinterpret_cast<char*>(&value), sizeof(value));\
  20. break;\
  21. }
  22. CASE_FIELD_TYPE(INT32, Int32, int);
  23. CASE_FIELD_TYPE(UINT32, UInt32, uint32_t);
  24. CASE_FIELD_TYPE(FLOAT, Float, float);
  25. CASE_FIELD_TYPE(DOUBLE, Double, double);
  26. CASE_FIELD_TYPE(BOOL, Bool, bool);
  27. CASE_FIELD_TYPE(INT64, Int64, int64_t);
  28. CASE_FIELD_TYPE(UINT64, UInt64, uint64_t);
  29. #undef CASE_FIELD_TYPE
  30. case google::protobuf::FieldDescriptor::CPPTYPE_ENUM: {
  31. int value = reflection->GetEnum(message, field)->number();
  32. int wsize = field->name().size();
  33. //写入name占用字节数
  34. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
  35. //写入name
  36. serialized_string->append(field->name().c_str(), field->name().size());
  37. wsize = sizeof(value);
  38. //写入value占用字节数
  39. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
  40. //写入value
  41. serialized_string->append(reinterpret_cast<char*>(&value), sizeof(value));
  42. break;
  43. }
  44. case google::protobuf::FieldDescriptor::CPPTYPE_STRING: {
  45. std::string value = reflection->GetString(message, field);
  46. int wsize = field->name().size();
  47. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
  48. serialized_string->append(field->name().c_str(), field->name().size());
  49. wsize = value.size();
  50. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
  51. serialized_string->append(value.c_str(), value.size());
  52. break;
  53. }
  54. case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: {
  55. std::string value;
  56. int wsize = field->name().size();
  57. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
  58. serialized_string->append(field->name().c_str(), field->name().size());
  59. const google::protobuf::Message& submessage = reflection->GetMessage(message, field);
  60. serialize_message(submessage, &value);
  61. wsize = value.size();
  62. serialized_string->append(reinterpret_cast<char*>(&wsize), sizeof(wsize));
  63. serialized_string->append(value.c_str(), value.size());
  64. break;
  65. }
  66. }
  67. }
  68. }
  69. }
3.2 parse_message

parse_message通过读取field/value,还原message对象。

主要思路跟serialize_message很像,通过Descriptor得到每个字段的描述符FieldDescriptor,通过Reflection的SetX填充message。

  1. void parse_message(const std::string& serialized_string, google::protobuf::Message* message) {
  2. const google::protobuf::Descriptor* descriptor = message->GetDescriptor();
  3. const google::protobuf::Reflection* reflection = message->GetReflection();
  4. std::map<std::string, const google::protobuf::FieldDescriptor*> field_map;
  5. for (int i = 0; i < descriptor->field_count(); ++i) {
  6. const google::protobuf::FieldDescriptor* field = descriptor->field(i);
  7. field_map[field->name()] = field;
  8. }
  9. const google::protobuf::FieldDescriptor* field = NULL;
  10. size_t pos = 0;
  11. while (pos < serialized_string.size()) {
  12. int name_size = *(reinterpret_cast<const int*>(serialized_string.substr(pos, sizeof(int)).c_str()));
  13. pos += sizeof(int);
  14. std::string name = serialized_string.substr(pos, name_size);
  15. pos += name_size;
  16. int value_size = *(reinterpret_cast<const int*>(serialized_string.substr(pos, sizeof(int)).c_str()));
  17. pos += sizeof(int);
  18. std::string value = serialized_string.substr(pos, value_size);
  19. pos += value_size;
  20. std::map<std::string, const google::protobuf::FieldDescriptor*>::iterator iter =
  21. field_map.find(name);
  22. if (iter == field_map.end()) {
  23. fprintf(stderr, "no field found.\n");
  24. continue;
  25. } else {
  26. field = iter->second;
  27. }
  28. assert(!field->is_repeated());
  29. switch (field->cpp_type()) {
  30. #define CASE_FIELD_TYPE(cpptype, method, valuetype)\
  31. case google::protobuf::FieldDescriptor::CPPTYPE_##cpptype: {\
  32. reflection->Set##method(\
  33. message,\
  34. field,\
  35. *(reinterpret_cast<const valuetype*>(value.c_str())));\
  36. std::cout << field->name() << "\t" << *(reinterpret_cast<const valuetype*>(value.c_str())) << std::endl;\
  37. break;\
  38. }
  39. CASE_FIELD_TYPE(INT32, Int32, int);
  40. CASE_FIELD_TYPE(UINT32, UInt32, uint32_t);
  41. CASE_FIELD_TYPE(FLOAT, Float, float);
  42. CASE_FIELD_TYPE(DOUBLE, Double, double);
  43. CASE_FIELD_TYPE(BOOL, Bool, bool);
  44. CASE_FIELD_TYPE(INT64, Int64, int64_t);
  45. CASE_FIELD_TYPE(UINT64, UInt64, uint64_t);
  46. #undef CASE_FIELD_TYPE
  47. case google::protobuf::FieldDescriptor::CPPTYPE_ENUM: {
  48. const google::protobuf::EnumValueDescriptor* enum_value_descriptor =
  49. field->enum_type()->FindValueByNumber(*(reinterpret_cast<const int*>(value.c_str())));
  50. reflection->SetEnum(message, field, enum_value_descriptor);
  51. std::cout << field->name() << "\t" << *(reinterpret_cast<const int*>(value.c_str())) << std::endl;
  52. break;
  53. }
  54. case google::protobuf::FieldDescriptor::CPPTYPE_STRING: {
  55. reflection->SetString(message, field, value);
  56. std::cout << field->name() << "\t" << value << std::endl;
  57. break;
  58. }
  59. case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE: {
  60. google::protobuf::Message* submessage = reflection->MutableMessage(message, field);
  61. parse_message(value, submessage);
  62. break;
  63. }
  64. default: {
  65. break;
  66. }
  67. }
  68. }
  69. }

protobuf反射详解的更多相关文章

  1. C#反射の反射详解

    C#反射の反射详解(点击跳转)C#反射の反射接口(点击跳转)C#反射反射泛型接口(点击跳转)C#反射の一个泛型反射实现的网络请求框架(点击跳转) 一.什么是反射 反射(Reflection):这是.N ...

  2. java 反射详解

    反射的概念和原理 类字节码文件是在硬盘上存储的,是一个个的.class文件.我们在new一个对象时,JVM会先把字节码文件的信息读出来放到内存中,第二次用时,就不用在加载了,而是直接使用之前缓存的这个 ...

  3. Java 反射详解 转载

    java 反射 定义 功能 示例 概要: Java反射机制详解 | |目录 1反射机制是什么 2反射机制能做什么 3反射机制的相关API ·通过一个对象获得完整的包名和类名 ·实例化Class类对象 ...

  4. ProtoBuf格式详解

    - 数据结构 通过前面的例子,可以看到PB的数据结构就是每项数据独立编码,包含一个表示数据类型 - Varint Varint是一种对数字进行编码的方法,将数字编码成不定长的二进制数据,数值越小,编码 ...

  5. java反射 详解!!!!

    java反射(特别通俗易懂) 反射是框架设计的灵魂 (使用的前提条件:必须先得到代表的字节码的Class,Class类用于表示.class文件(字节码)) 一.反射的概述 JAVA反射机制是在运行状态 ...

  6. protobuf使用详解

    https://blog.csdn.net/skh2015java/article/details/78404235 原文地址:http://blog.csdn.net/lyjshen/article ...

  7. 【原创】go语言学习(十八)反射详解

    目录 变量介绍 反射介绍 结构体反射 反射总结以及应用场景 变量介绍 1.变量的内在机制 A. 类型信息,这部分是元信息,是预先定义好的B. 值类型,这部分是程序运行过程中,动态改变的 var arr ...

  8. java反射详解

    本篇文章依旧采用小例子来说明,因为我始终觉的,案例驱动是最好的,要不然只看理论的话,看了也不懂,不过建议大家在看完文章之后,在回过头去看看理论,会有更好的理解. 下面开始正文. [案例1]通过一个对象 ...

  9. java反射详解(转)

    本篇文章依旧采用小例子来说明,因为我始终觉的,案例驱动是最好的,要不然只看理论的话,看了也不懂,不过建议大家在看完文章之后,在回过头去看看理论,会有更好的理解. 下面开始正文. [案例1]通过一个对象 ...

随机推荐

  1. ViewPage第二课为ViewPage加入标题

    在第一课 学前准备:掌握ViewPage第一课http://blog.csdn.net/wei_chong_chong/article/details/50468832 为ViewPage加入标题: ...

  2. libjpeg用法

    libjpeg是一个完全用C语言编写的库,包含了被广泛使用的JPEG解码.JPEG编码和其他的JPEG功能的实现.这个库由独立JPEG工作组维护.最新版本号是6b,于1998年发布.可以参考维基百科关 ...

  3. apache与IIS共用80端口冲突解决方法

    如果同一台电脑安装了apache和iis,会提示80端口冲突,如何解决apache与iis 80端口冲突的问题呢,并且同时使用apache和iis 将apache设为使用80端口,IIS使用其它端口, ...

  4. 英特尔投资:7200万美元投资12家创新公司,包括3家中国公司(www.intelcapital.com)

    集微网消息,英特尔投资——英特尔公司全球投资机构,今天在英特尔投资全球峰会上宣布向12家科技创业公司投资超过7200万美元.加上今天宣布的新投资,英特尔投资在2018年投资总额已超过1.15亿美元. ...

  5. Compmgmtlauncher.exe问题解决方法

    修改注册表:HKEY_CLASSES_ROOT\CLSID\{20D04FE0-3AEA-1069-A2D8-08002B30309D}\shell\Manage\command   原来的默认键值为 ...

  6. LeetCode解题报告--2Sum, 3Sum, 4Sum, K Sum求和问题总结

    前言: 这几天在做LeetCode 里面有2sum, 3sum(closest), 4sum等问题, 这类问题是典型的递归思路解题.该这类问题的关键在于,在进行求和求解前,要先排序Arrays.sor ...

  7. mac系统创建.开头文件.htaccess

    thinkphp5 隐藏index.php的时候需要用的.htaccess文件,但是mac默认不让创建这种文件 感谢 https://blog.csdn.net/gyz413977349/articl ...

  8. COCOS2D-X 3.0在MAC下创建新IOS项目:

    首先进入:CocoStudio\Source\3.0\cocos2d-x\tools\cocos2d-console\bin 运行 ./cocos new -p com.aaaa -l cpp MyG ...

  9. thinkphp3.1 发送email

    //*********************发送邮件************************** Vendor('email'); //******************** 配置信息 * ...

  10. RESET MASTER 和RESET SLAVE 命令的使用方法 注意事项

    RESET MASTER 删除所有index file 中记录的所有binlog 文件,将日志索引文件清空,创建一个新的日志文件,这个命令通常仅仅用于第一次用于搭建主从关系的时的主库,注意  rese ...