ProtoBuf—— 反射原理解析

  • ProtoBuf—— 反射原理
    • 1、反射原理
      • 1.1、反射机制的背景
      • 1.2、定义
      • 1.3、反射原理关注的一些问题
      • 1.4、反射原理的优势和应用
    • 2、ProtoBuf反射原理——获取并改造 元信息
      • 2.1 、 .proto 文件
      • 2.2 、 反射原理过程
      • 2.3 、 反射相关的类和API
        • 2.3.1、google::protobuf::Message
        • 2.3.2、 google::protobuf::Descriptor
        • 2.3.3、 google::protobuf::Reflection
        • 2.3.4、 google::protobuf::FieldDescriptor
        • 2.3.5、 其他相关接口
          • 2.3.5.1、DescriptorPool(元信息池)
          • 2.3.5.2、DescriptorDatabase( .proto 文件库)
          • 2.3.5.3、MessageFactory( 实例工厂)
      • 2.4 、 反射原理过程具体解析
        • 2.4.1、获取元信息
        • 2.4.2、创建和获取实例(查表)
        • 2.4.3、实例对象的读写
      • 2.5 、 反射原理具体实例介绍
  • 参考

ProtoBuf—— 反射原理

1、反射原理

1.1、反射机制的背景

有关某个外部世界表示”的计算过程, 并通过它来对那个外部世界进行推理; 那么我们也可以构造能够对自身表示和计算进行推理的计算过程,它包含负责管理有关自身的操作和结构表示的内部过程。
—— 1982年 Smith, Brian Cantwell 博士论文首次提出

  • 所谓编程实际上就是在构造 “关于外部世界” 的计算过程。如果用 F 表示这个构造过程,用 X表示外部世界,那么编写一个计算系统可表示为 F(X)
  • 我们完全可以构造对上述过程本身进行描述和推理的计算过程。即将 F(X) 视为新的 “世界” 和 研究对象,构造 F(F(X))
    • 所以我们为了构建和改造世界,前提需要充分认识世界

计算机程序在运行时可以访问、检测和修改它本身状态或行为
—— 反射 (计算机科学) Wikipedia

我们平时编写的计算系统是面向特定领域的(通常是面向现实建模), 系统中包含 用来描述领域中的实体实体间关系 的数据结构以及处理这些数据结构的规则。那么反射系统面向领域便是这个系统本身。

1.2、定义

在对程序本身进行构建的同时,在运行时可以访问和修改自身状态和行为,这建立在对系统系统元信息充分感知的基础上。

系统元信息:

元信息:即系统自描述信息,用于描述系统本身。举例来讲,即系统有哪些类?类中有哪些字段、哪些方法?字段属于什么类型、方法又有怎样的参数和返回值?

反射则提供了封装程序集、模块和类型的对象。您可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。然后,可以调用类型的方法或访问其字段和属性。

1.3、反射原理关注的一些问题

  • 1、如何在程序运行过程中通过类型名字(一个字符串,合法但是内容在编译期间未知,比如是在配置文件中获取的)创建出类型对象.
  • 2、如何在程序运行过程中通过对象和对象的属性的名字(一个字符串,合法但是内容在编译期间未知,比如是通过通讯包获取的)获取,修改对应属性.
  • 3、如何在程序运行过程中通过对象和对象方法的名字(一个字符串,合法但是内容在编译期间未知,比如是从用户输入获取的)调用对应的方法.

1.4、反射原理的优势和应用

优势:
反射在运行时检测或修改程序行为的程序中,提高了程序的灵活性和扩展性,降低耦合性,提高自适应能力。它允许程序创建和控制任何类的对象,无需提前硬编码目标类.
应用:

  • 1、多并发的服务器框架
  • 2、网络序列化数据传输方法等
  • 3、微软的MFC

在开发编辑器的阶段,并没有相应的类,但是使用者想通过在编辑器中传入类的名字,然后在开发中,根据编辑器传入的类名,新建一个类去实现的话,这种方法非常适用。

2、ProtoBuf反射原理——获取并改造 元信息

2.1 、 .proto 文件

ProtoBuf 反射所需的元信息在哪?答案便是使用 ProtoBuf 的第一步就会接触到的:.proto 文件。

  • .proto 文件

    • 使用 ProtoBuf 内置的工具 protoc 编译器编译,protoc 将 .proto 文件内容编码并写入生成的代码中(.pb.cc文件)
    • 使用 ProtoBuf 提供的编译 API 在运行时手动(指编码)解析 .proto 文件内容。实际上 protoc底层调用的也正是这个编译 API。
// 在 xxx.proto 文件中定义 Example1 message
message Example1 {optional string stringVal = 1;optional bytes bytesVal = 2;message EmbeddedMessage {int32 int32Val = 1;string stringVal = 2;}optional EmbeddedMessage embeddedExample1 = 3;repeated int32 repeatedInt32Val = 4;repeated string repeatedStringVal = 5;
}
//提供我们数据元信息的过程,这些元信息包括数据由哪些字段构成,字段又属于什么类型以及字段之间的组合关系等

2.2 、 反射原理过程

无论 .proto 文件来源于何处,我们都需要对其做进一步的处理,将其解析成内存对象,并构建其与实例的映射,同时也要计算每个字段的内存偏移:

  • 1、提供 .proto (范指 ProtoBuf Message 语法描述的元信息)
  • 2、解析 .proto 构建 FileDescriptor、FieldDescriptor 等,即 .proto 对应的内存模型(对象)
  • 3、之后每创建一个实例,就将其存到相应的实例池中
  • 4、将 Descriptorinstance 的映射维护到表中备查
  • 5、通过 Descriptor 可查到相应的 instance,又由于了解 instance中字段类型(FieldDescriptor),所以知道字段的内存偏移,那么就可以访问或修改字段的值
    • 任何一个对象最终都对应一段内存,有内存起始(start_addr)和结束地址, 而对象的每一个属性,都位于start_addr+$offset ,所以当对象和对应属性的offset已知的时候, 属性的内存地址也就是可以获取的。

2.3 、 反射相关的类和API

  • 1、google::protobuf::Message
  • 2、google::protobuf::Descriptor
  • 3、google::protobuf::Reflection
  • 4、google::protobuf::FieldDescriptor

    Person是自定义的protobuf类型

2.3.1、google::protobuf::Message

  • Person是自定义的pb类型,继承自Message.
  • MessageLite作为Message基类,更加轻量级一些。
  • 通过Message的两个接口GetDescriptor/GetReflection,可以获取该类型对应的Descriptor/Reflection。
//google::protobuf::Message
const Descriptor* GetDescriptor() const;
const Reflection* GetReflection() const;

2.3.2、 google::protobuf::Descriptor

Descriptor是对message类型定义的描述,包括message的名字、所有字段的描述、原始的proto文件内容等。

//Descriptor
//返回message的属性个数
int field_count() const;
//返回第index个属性
const FieldDescriptor* field(int index) const;
//通过proto文件里面定义的tag, 返回属性
const FieldDescriptor* FindFieldByNumber(int number) const;
//通过属性名,返回属性
const FieldDescriptor* FindFieldByName(const std::string& name) const;
//通过小写的属性名,返回属性
const FieldDescriptor* FindFieldByLowercaseName(const std::string& lowercase_name) const;
//通过驼峰属性名,返回属性
const FieldDescriptor* FindFieldByCamelcaseName(const std::string& camelcase_name) const;
//返回枚举类型的数量
int enum_type_count() const;
//返回第index个枚举类型属性
const EnumDescriptor* enum_type(int index) const;
//通过名称,返回枚举属性
const EnumDescriptor* FindEnumTypeByName(const std::string& name) const;
//通过属性值,返回枚举值属性
const EnumValueDescriptor* FindEnumValueByName(const std::string& name) const;

2.3.3、 google::protobuf::Reflection

  • Reflection:接口类,主要提供了动态读写pb字段的接口,对pb对象的自动读写主要通过该类完成。
  • 提供方法来动态访问/修改message中的field的接口类,对每种类型,Reflection都提供了一个单独的接口用于读写字段对应的值。
//检查message中的非repeated属性是否存在
bool HasField(const Message& message, const FieldDescriptor* field) const;
//返回message中repeated属性的长度
int FieldSize(const Message& message, const FieldDescriptor* field) const;
//清除message中的属性,非repeated属性HashField返回false,repeated属性FieldSize返回0
void ClearField(Message* message, const FieldDescriptor* field) const;
//返回message中除Unknown的属性,包括所有HashField返回为true,FieldSize非0的属性
void ListFields(const Message& message,std::vector<const FieldDescriptor*>* output) const;
//返回message对应属性的,对应类型的返回值
int32 GetInt32(const Message& message, const FieldDescriptor* field) const;int64 GetInt64(const Message& message, const FieldDescriptor* field) const;uint32 GetUInt32(const Message& message, const FieldDescriptor* field) const;uint64 GetUInt64(const Message& message, const FieldDescriptor* field) const;float GetFloat(const Message& message, const FieldDescriptor* field) const;double GetDouble(const Message& message, const FieldDescriptor* field) const;bool GetBool(const Message& message, const FieldDescriptor* field) const;std::string GetString(const Message& message,const FieldDescriptor* field) const;const EnumValueDescriptor* GetEnum(const Message& message,const FieldDescriptor* field) const;//返回message,属性的value的int类型值
int GetEnumValue(const Message& message, const FieldDescriptor* field) const;
const Message& GetMessage(const Message& message,const FieldDescriptor* field,MessageFactory* factory = nullptr) const;//设置message,对应属性的对应类型的值
void SetInt32(Message* message, const FieldDescriptor* field,int32 value) const;void SetInt64(Message* message, const FieldDescriptor* field,int64 value) const;void SetUInt32(Message* message, const FieldDescriptor* field,uint32 value) const;void SetUInt64(Message* message, const FieldDescriptor* field,uint64 value) const;void SetFloat(Message* message, const FieldDescriptor* field,float value) const;void SetDouble(Message* message, const FieldDescriptor* field,double value) const;void SetBool(Message* message, const FieldDescriptor* field,bool value) const;void SetString(Message* message, const FieldDescriptor* field,const std::string& value) const;void SetEnum(Message* message, const FieldDescriptor* field,const EnumValueDescriptor* value) const;void SetEnumValue(Message* message, const FieldDescriptor* field,int value) const;Message* MutableMessage(Message* message, const FieldDescriptor* field,MessageFactory* factory = nullptr) const;//返回message对应属性repeated字段的index位置的值
int32 GetRepeatedInt32(const Message& message, const FieldDescriptor* field,int index) const;int64 GetRepeatedInt64(const Message& message, const FieldDescriptor* field,int index) const;uint32 GetRepeatedUInt32(const Message& message, const FieldDescriptor* field,int index) const;uint64 GetRepeatedUInt64(const Message& message, const FieldDescriptor* field,int index) const;float GetRepeatedFloat(const Message& message, const FieldDescriptor* field,int index) const;double GetRepeatedDouble(const Message& message, const FieldDescriptor* field,int index) const;bool GetRepeatedBool(const Message& message, const FieldDescriptor* field,int index) const;std::string GetRepeatedString(const Message& message,const FieldDescriptor* field, int index) const;const EnumValueDescriptor* GetRepeatedEnum(const Message& message,const FieldDescriptor* field,int index) const;int GetRepeatedEnumValue(const Message& message, const FieldDescriptor* field,int index) const;const Message& GetRepeatedMessage(const Message& message,const FieldDescriptor* field,int index) const;
//设置message的repeated属性的index位置对应类型的value的值
void SetRepeatedInt32(Message* message, const FieldDescriptor* field,int index, int32 value) const;void SetRepeatedInt64(Message* message, const FieldDescriptor* field,int index, int64 value) const;void SetRepeatedUInt32(Message* message, const FieldDescriptor* field,int index, uint32 value) const;void SetRepeatedUInt64(Message* message, const FieldDescriptor* field,int index, uint64 value) const;void SetRepeatedFloat(Message* message, const FieldDescriptor* field,int index, float value) const;void SetRepeatedDouble(Message* message, const FieldDescriptor* field,int index, double value) const;void SetRepeatedBool(Message* message, const FieldDescriptor* field,int index, bool value) const;void SetRepeatedString(Message* message, const FieldDescriptor* field,int index, const std::string& value) const;void SetRepeatedEnum(Message* message, const FieldDescriptor* field,int index, const EnumValueDescriptor* value) const;void SetRepeatedEnumValue(Message* message, const FieldDescriptor* field,int index, int value) const;Message* MutableRepeatedMessage(Message* message,const FieldDescriptor* field,int index) const;

2.3.4、 google::protobuf::FieldDescriptor

FieldDescriptor描述message中的单个字段,例如字段名,字段属(optional/required/repeated)等。对于proto定义里的每种类型,都有一种对应的C++类型,

  //是否是必须的bool is_required() const;  // shorthand for label() == LABEL_REQUIRED//是否是可选的bool is_optional() const;  // shorthand for label() == LABEL_OPTIONAL//是否是repeatedbool is_repeated() const;  // shorthand for label() == LABEL_REPEATED//是否可以packbool is_packable() const;  // shorthand for is_repeated() &&//               IsTypePackable(type())//是否是packedbool is_packed() const;    // shorthand for is_packable() &&//               options().packed()//是否是messagebool is_map() const;       // shorthand for type() == TYPE_MESSAGE &&// message_type()->options().map_entry()//属性在message的索引位置int index() const;

2.3.5、 其他相关接口

2.3.5.1、DescriptorPool(元信息池)
  • 任何时候想要查询一个Descriptor , 都是去DescriptorPool里面查询。
  • 缓存所有查询的文件的Descriptor 。对外提供了诸如 FindServiceByNameFindMessageTypeByName等各类接口以便外部查询所需的元信息。
  • 当 DescriptorPool 不存在时需要查询的元信息时,将进一步到 DescriptorDatabase 中去查找。
2.3.5.2、DescriptorDatabase( .proto 文件库)
  • DescriptorDatabase是一个纯虚基类,描述了一系列符合通过名字(文件名,符号名。。。)
    来获取FileDescriptorProto的接口
  • 可从硬编码或磁盘中查询对应名称的 .proto 文件内容,解析后返回查询需要的元信息。
// 这里我干掉了里面的英文注释.
class LIBPROTOBUF_EXPORT DescriptorDatabase {public:inline DescriptorDatabase() {}virtual ~DescriptorDatabase();virtual ~DescriptorDatabase();// 通过文件名字找.virtual bool FindFileByName(const string& filename,FileDescriptorProto* output) = 0;// 通过符号名字找.virtual bool FindFileContainingSymbol(const string& symbol_name,FileDescriptorProto* output) = 0;// 通过扩展信息找.virtual bool FindFileContainingExtension(const string& containing_type,int field_number,FileDescriptorProto* output) = 0;// 通过扩展信息的tag数字找...virtual bool FindAllExtensionNumbers(const string& /* extendee_type */,vector<int>* /* output */) {return false;}private:GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(DescriptorDatabase);
};

核心的两个派生类是 :

  • EncodedDescriptorDatabase

    • 支持DescriptorDatabase的全部接口
    • 接收序列化之后的FileDescriptorProto, 保存在map中备查.
    • 这个类对应着预先编译链接好的那些类型的反射机制。
  • SourceTreeDescriptorDatabase
    • 仅支持DescriptorDatabaseFindFileByName接口。其余直接返回false.
    • 每次查询某个文件都是从磁盘读入proto的源文件,编译解析后返回对应的FileDescriptorProto .
    • 这个类对应着动态编译proto源文件的时候的反射机制.
2.3.5.3、MessageFactory( 实例工厂)

任何时候想要获取一个类型的instance , 都要去MessageFactory里面获取。
MessageFactory 是一个纯虚的基类,定义了通过Descripor来获取对应类型instance的接口.

{public:inline MessageFactory() {}virtual ~MessageFactory();// 了通过Descripor来获取对应类型instance 的接口virtual const Message* GetPrototype(const Descriptor* type) = 0;// 这个是获取编译链接好的那些类型的factory单例的入口.static MessageFactory* generated_factory();// 这个是对应的像上面哪个单例内填装数据的接口,protoc自动生成的文件都会有调用.static void InternalRegisterGeneratedMessage(const Descriptor* descriptor,const Message* prototype);private:GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(MessageFactory);
}

同样有两个核心的派生类

  • GeneratedMessageFactory

    • 一个map , 保存着DescriptorMessage
    • 这个类对应着预先编译链接好的那些类型的反射机制。
  • DynamicMessageFactory
    • 有简单的缓存,保存自己解析过的Descriptor`` < /li >
    • < li >可以通过Descriptor,动态的基于内存构造出一个Message

2.4 、 反射原理过程具体解析

/* 反射创建实例 */
auto descriptor = google::protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName("Dog");
auto prototype = google::protobuf::MessageFactory::generated_factory()->GetPrototype(descriptor);
auto instance = prototype->New();/* 反射相关接口 */
auto reflecter = instance.GetReflection();
auto field = descriptor->FindFieldByName("name");
reflecter->SetString(&instance, field, "鸡你太美") ;// 获取属性的值.
std::cout<<reflecter->GetString(instance , field)<< std::endl ;
return 0 ;

2.4.1、获取元信息

  • 数据存储在哪里

    • 1、所有的Descriptor存储在单例的DescriptorPool 中。

      • google::protobuf::DescriptorPool::generated_pool()来获取他的指针。
    • 2、所有的instance 存储在单例的MessageFactory中。
      • google::protobuf::MessageFactory::generated_factory()来获取他的指针。
    • 3、将所有的Descriptor & instance 提前维护到表中备查

DescriptorPool 相当于缓存了文件的 Descriptor(底层使用 Map),查询时将先到缓存中查询,如果未能找到再进一步到 DB 中(即 DescriptorDatabase)查询,此时可能需要从磁盘中读取文件内容,然后再解析成 Descriptor 返回,这里需要消耗一定的时间。

不难看出,DescriptorPoolDescriptorDatabase 通过缓存机制提高了反射运行效率,但这只是反射工程实现上的一种优化,我们更感兴趣的应该是 Descriptor 的来源。

DescriptorDatabase 从磁盘中读取 .proto 内容并解析成 Descriptor 这一来源很容易理解,但我们大多数时候并不会采用这种方式,反射时也不会去读取 .proto 文件。那么我们的 .proto 内容在哪?

实际上我们在使用 protoc 生成 xxx.pb.cc 和 xxx.pb.h 文件时,其中不仅仅包含了读写数据的接口,还包含了 .proto 文件内容。阅读任意一个 xxx.pb.cc 的内容,你可以看到如下类似代码

static void AddDescriptorsImpl() {InitDefaults();// .proto 内容static const char descriptor[] GOOGLE_PROTOBUF_ATTRIBUTE_SECTION_VARIABLE(protodesc_cold) = {"\n\022single_int32.proto\"\035\n\010Example1\022\021\n\010int3""2Val\030\232\005 \001(\005\" \n\010Example2\022\024\n\010int32Val\030\377\377\377\377""\001 \003(\005b\006proto3"};// 注册 descriptor::google::protobuf::DescriptorPool::InternalAddGeneratedFile(descriptor, 93);// 注册 instance::google::protobuf::MessageFactory::InternalRegisterGeneratedFile("single_int32.proto", &protobuf_RegisterTypes);
}

其中 descriptor 数组存储的便是 .proto 内容。这里当然不是简单的存储原始文本字符串,而是经过了 SerializeToString 序列化处理,而后将结果以硬编码的形式保存在 xxx.pb.cc

硬编码的 .proto 元信息内容将以懒加载(类似于单例模式中的懒汉模式)的方式(被调用时才触发)被 DescriptorDatabase 加载、解析,并缓存到 DescriptorPool 中。

2.4.2、创建和获取实例(查表)

根据 MessageFactory 获得了一个实例。MessageFactory 是实例工厂,对外提供了根据元信息 descriptor 获取相应实例的能力。

// 注册对应 descriptor 的 instance 到 MessageFactory
// InternalRegisterGeneratedFile 函数内部,会将创建一个实例并做好 descriptor 与 instance 的映射
// xxx 应该替换为文件名,根据自己的需求取`在这里插入代码片`
namespace {//! 将本文件内的全部类型的instance注册进入MessageFactory的接口.
void protobuf_RegisterTypes(const ::std::string&) {// 初始化本文件的reflection数据.protobuf_AssignDescriptorsOnce();::google::protobuf::MessageFactory::InternalRegisterGeneratedMessage(Test_descriptor_, &Test::default_instance());
}
//! 本文件的初始接口.
void protobuf_AddDesc_xxx_2eproto() {static bool already_here = false;if (already_here) return;already_here = true;GOOGLE_PROTOBUF_VERIFY_VERSION;// 注册本文件的Descriptor包. 这样就可以用名字通过generated_pool获取对应的Descriptor。::google::protobuf::DescriptorPool::InternalAddGeneratedFile("\n\013xxx.proto\022\001T\"\022\n\004Test\022\n\n\002id\030\001 \001(\005", 36);// 将本文件的类型instance注册接口注册给MessageFactory.// 这里注册接口是为了实现类型的lazy注册。如果没有使用请求某个文件的类型,就不注册对应文件的类型。::google::protobuf::MessageFactory::InternalRegisterGeneratedFile("xxx.proto", &protobuf_RegisterTypes);// 构造并且初始化全部instance.Test::default_instance_ = new Test();Test::default_instance_->InitAsDefaultInstance();// 注册清理接口.::google::protobuf::internal::OnShutdown(&protobuf_ShutdownFile_xxx_2eproto);
}
//! 下面利用全局变量的构造函数确保main函数执行之前数据已经进行注册.
struct StaticDescriptorInitializer_xxx_2eproto {StaticDescriptorInitializer_xxx_2eproto() {protobuf_AddDesc_xxx_2eproto();}
} static_descriptor_initializer_xxx_2eproto_;
}

每次构建实例后,都将 descriptor 和 instance 维护到一个 _table 中,即映射表以便获取。后续所谓通过反射获得某个类的某个实例子,实际就是查表的过程。

2.4.3、实例对象的读写

实例对象的 reflection 里面存储了对象属性的偏移地址,而这些信息其实与 .proto 内容信息一样,在 protoc 编译时通过解析 proto 文件内容获得且记录在 xxx.pb.cc 中。

有了属性的内存偏移,自然可以对属性进行读写操作

  • 任何一个对象最终都对应一段内存,有内存起始(start_addr)和结束地址, 而对象的每一个属性,都位于start_addr+$offset ,所以当对象和对应属性的offset已知的时候, 属性的内存地址也就是可以获取的。
  • GeneratedMessageReflection 的填装和获取
    对于每一个message , 都有一个对应的GeneratedMessageReflection 对象.
    这个对象保存了对应message反射操作需要的信息.

//!初始化本文件的所有GeneratedMessageReflection对象.
void protobuf_AssignDesc_xxx_2eproto() {protobuf_AddDesc_xxx_2eproto();const ::google::protobuf::FileDescriptor* file =::google::protobuf::DescriptorPool::generated_pool()->FindFileByName("xxx.proto");GOOGLE_CHECK(file != NULL);Test_descriptor_ = file->message_type(0);static const int Test_offsets_[1] = {//这里在计算属性的内存偏移.GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Test, id_),};// 这里是个test包填装的GeneratedMessageReflection对象.Test_reflection_ =new ::google::protobuf::internal::GeneratedMessageReflection(Test_descriptor_,Test::default_instance_,Test_offsets_,GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Test, _has_bits_[0]),GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Test, _unknown_fields_),-1,::google::protobuf::DescriptorPool::generated_pool(),::google::protobuf::MessageFactory::generated_factory(),sizeof(Test));
}
inline void protobuf_AssignDescriptorsOnce() {::google::protobuf::GoogleOnceInit(&protobuf_AssignDescriptors_once_,&protobuf_AssignDesc_xxx_2eproto);
}// message.h 中 message的基本接口.
virtual const Reflection* GetReflection() const {return GetMetadata().reflection;
}
// 每个message获取自己基本信息的接口.
::google::protobuf::Metadata Test::GetMetadata() const {protobuf_AssignDescriptorsOnce();::google::protobuf::Metadata metadata;metadata.descriptor = Test_descriptor_;metadata.reflection = Test_reflection_;return metadata;
}

内存赋值,可以执行修改操作:


// 找到对应的内存地址,返回合适类型的指针.
template <typename Type>
inline Type* GeneratedMessageReflection::MutableRaw(Message* message, const FieldDescriptor* field) const {int index = field->containing_oneof() ?descriptor_->field_count() + field->containing_oneof()->index() :field->index();void* ptr = reinterpret_cast<uint8*>(message) + offsets_[index];return reinterpret_cast<Type*>(ptr);
}
// 设置protobuf的标志bit.
inline void GeneratedMessageReflection::SetBit(Message* message, const FieldDescriptor* field) const {if (has_bits_offset_ == -1) {return;}MutableHasBits(message)[field->index() / 32] |= (1 << (field->index() % 32));
}
// 设置某个字段的值
template <typename Type>
inline void GeneratedMessageReflection::SetField(Message* message, const FieldDescriptor* field, const Type& value) const {if (field->containing_oneof() && !HasOneofField(*message, field)) {ClearOneof(message, field->containing_oneof()); // V3 oneof 类型的清理。}*MutableRaw<Type>(message, field) = value; // 先直接覆盖field->containing_oneof() ?SetOneofCase(message, field) : SetBit(message, field); // 添加标记bit
}

2.5 、 反射原理具体实例介绍

参考
下面的代码展示了protobuf 对象反射的例子。将一个对象按照反射的字段顺序序列化到string,然后反序列化到对象。最后调用反射打印其字段值,可以看到对象能够还原。

1、proto文件

package tutorial;
message Person {required string name = 1;required int32 id = 2;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;}
#include <string>
#include <map>
#include <iostream>
#include <stdio.h>
#include "person.pb.h"
using namespace tutorial;
using namespace google::protobuf;using std::cout;
using std::endl;
using std::string;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;}}}}
}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;}}}
}void print_field(const Message& message)
{const Descriptor* descriptor = message.GetDescriptor();const Reflection* reflection = message.GetReflection();for (int i = 0; i < descriptor->field_count(); ++i) {const FieldDescriptor* field = descriptor->field(i);bool has_field = reflection->HasField(message, field);assert(!field->is_repeated());switch (field->cpp_type()) {
#define CASE_FIELD_TYPE(cpptype, method, valuetype)\case FieldDescriptor::CPPTYPE_##cpptype:{\valuetype value = reflection->Get##method(message, field);\if (has_field) {\cout << field->name() << " : " << value << ", type : " << #valuetype << "\n";\}  else  {\cout << field->name() << " : " << "None" << ", type : " << #valuetype << "\n";\}\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 FieldDescriptor::CPPTYPE_ENUM: {int value = reflection->GetEnum(message, field)->number();if (has_field)  {cout << field->name() << " : " << value << ", type : " << "enum \n";    }else  {cout << field->name() << " : " << "None" << ", type : " << "enum \n";}break;}case FieldDescriptor::CPPTYPE_STRING: {string value = reflection->GetString(message, field);if (has_field)  {cout << field->name() << " : " << value << ", type : " << "string \n";  }else  {cout << field->name() << " : " << "None" << ", type : " << "string \n";}break;}case FieldDescriptor::CPPTYPE_MESSAGE: {const Message& submessage = reflection->GetMessage(message, field);print_field(submessage);break;}}}
}int main()
{string str;Person person;person.set_name("shonm");person.set_id(123);person.mutable_phone()->set_number("1380000");person.mutable_phone()->set_type(Person_PhoneType_WORK);serialize_message(person, &str);       //按照自己的方式(反射的字段)序列化Person person2;parse_message(str, &person2);          //按照自己的方式反序列化printf("\n\n");print_field(person);                   //根据反射打印字段printf("\n\n");print_field(person2);
}

参考

1、https://www.jianshu.com/p/ddc1aaca3691
2、https://blog.csdn.net/cchd0001/article/details/52452204
3、https://blog.csdn.net/yindongjie1221/article/details/90575989
4、https://blog.csdn.net/boshuzhang/article/details/64920286
5、https://blog.csdn.net/zxm342698145/article/details/83931027

ProtoBuf(Google Protocol Buffers)—— 反射原理以及反射具体流程介绍相关推荐

  1. Google Protocol Buffers介绍

    Google Protocol Buffers(简称Protobuf),是Google的一个开源项目,它是一种结构化数据存储格式,是Google公司内部的混合语言数据标准,是一个用来序列化(将对象的状 ...

  2. 还在用JSON? Google Protocol Buffers 更快更小 (原理篇)

    欢迎关注微信公众号「随手记技术团队」,查看更多随手记团队的技术文章.转载请注明出处 本文作者:丁同舟 原文链接:mp.weixin.qq.com/s/cyOHe1LS-- 背景 随手记客户端与服务端交 ...

  3. Google Protocol Buffers 2.3.0 for java 快速开始

    Google Protocol Buffers 2.3.0 for java 快速开始 博客分类: Java JavaGoogleUbuntuLinux数据结构 Protocol Buffers是一个 ...

  4. Google Protocol Buffers 之.Net应用

    Google Protocol Buffers 之.Net应用 吴剑 2011-2-22 原创文章,转载必需注明出处:http://www.cnblogs.com/wu-jian/ 前言 最近接到一个 ...

  5. Google Protocol Buffers三两事【知识笔记】

    目录 一.亮点简介 二.使用指南1.定义.proto文件2.编译.proto文件3.读写数据 三.本文总结 四.参考资料 一.亮点简介 Protocol Buffers一种结构化数据存储格式.特点:快 ...

  6. Google Protocol Buffers浅析(四)

    本文作为结束篇,会稍微介绍下怎么反序列化GoogleBuffer数据,并在最后提供本系列文章中所用到的代码整理供下载. 上一篇文章介绍了怎样将数据序列化到了addressbook.data中,那么对于 ...

  7. Google Protocol Buffers和java字符串处理控制

    大多数的操作码被从夜晚复制.懒得敲. 直接在源代码和测试结果如下. serabuffer.proto档.使用下面的命令来生成java代码. protoc -I=./ --java_out=./ ser ...

  8. [转]Google Protocol Buffers 之.Net应用

    吴剑 2011-2-22 wu-jian.cnblogs.com 前言 最近接到一个跨平台的测试项目,服务端Linux,是Java开发的一系列Socket接口,客户端Windows,所以准备用.Net ...

  9. Protocol Buffers C++ 入门教程

    文章目录 1.ProtoBuf 简介 2.序列化和反序列化 2.1 简介 2.2 JSON 简介 2.3 使用 JSON 进行序列化和反序列化 2.4 C++ 对象序列化常用方法 2.3.1 XML ...

最新文章

  1. 嵌入式系统学习笔记之五-- uboot常用命令 环境变量
  2. 阅读之spring+Dubbo
  3. 一道关于Promise应用的面试题
  4. 三分钟看懂一致性哈希算法
  5. 在职研究生和全日制研究生的区别_“在职研究生”与“全日制研究生”有什么区别?...
  6. 【每日SQL打卡】​​​​​​​​​​​​​​​DAY 12丨游戏玩法分析 V【难度困难】
  7. Skip level 1 on 1
  8. Flutte的ListView不能直接嵌套ListView解决办法
  9. 自己动手实现SharePointList的分页展示
  10. 猫鼠游戏之勒索病毒来袭
  11. 熊猫的python小课账号_校长,我要上车——python模拟登录熊猫TV
  12. 微信商户号转账到个人银行卡加密算法及结果查询问题
  13. 操作日志 | 上证50是续命仙丹还是饮鸩毒药?
  14. 用JAVA 创建自己的 A4 打印语言
  15. pacs系统片子往服务器传输方式,PACS系统-医学影像的传输.ppt
  16. 基于thinkphp5的简单的下拉菜单二级联动
  17. ChatGPT初体验-帮我写一本奇幻冒险小说(Aethia的勇士)
  18. c++的加密库--crypto++/openssl库
  19. python之decimal
  20. C语言二分法查找详解

热门文章

  1. 最新麦子学院Web前端项目实战 Web前端开发从入门到精通33G完整版
  2. 必修三计算机选修三知识点,高二数学必修三知识点归纳总结
  3. 一台电脑能运行两个mysql数据库_一台电脑可以同时装两个带有SQL数据库的软件吗...
  4. 基于DDR3的串口传图帧缓存系统设计实现
  5. dw网页插入java小程序_DW网页设计35:构建Java插入模块
  6. Android adb 设置和获取系统属性
  7. 【整理】HLS视频协议第二弹--裁剪部分视频及m3u8文件,编写通用客户端以播放m3u8视频
  8. 元音音频时域频域特点 RGB文件三通道熵计算
  9. JVM从跨平台到跨专业 Ⅲ --编译期处理、类加载、JVM调优
  10. iPhone X 媒体查询适配