所谓反射机制,就是能够在运行时知道任意类的所有属性和方法,能够调用任意对象的任意方法和属性。这种动态获取的信息以及动态调用对象方向的功能称为反射机制。
不像Jave等语言,C++本身没有反射机制,在使用C++版本Protobuf时,protobuf通过proto文件产生响应的message和service,protobuf可以通过proto文件提供反射机制,程序在运行时可以通过proto获取任意message和任意service的属性和方法,也可以在运行时调用message的属性和方法。
1.获取message和service的属性和方法
protobuf通过Descriptor获取任意message或service的属性和方法,Descriptor主要包括了一下几种类型:
FileDescriptor | 获取Proto文件中的Descriptor和ServiceDescriptor |
Descriptor | 获取类message属性和方法,包括FieldDescriptor和EnumDescriptor |
FieldDescriptor | 获取message中各个字段的类型、标签、名称等 |
EnumDescriptor | 获取Enum中的各个字段名称、值等 |
ServiceDescriptor | 获取service中的MethodDescriptor |
MethodDescriptor | 获取各个RPC中的request、response、名称等 |
也就是说,如果能够获取到proto文件的FileDescriptor,就能获取proto文件中的所有的内容。那么如何获取proto文件的FileDescriptor呢?protobuf提供两种方法。
1.1 使用protoc将proto文件生成.h和.cc文件
这种法法直接根据生成的类来获取响应的FileDescriptor,比如现在有test.proto文件,那么可以通过DescriptorPool::generated_pool()获取到其FileDescriptor
const FileDescriptor* fileDescriptor = DescriptorPool::generated_pool()->FindFileByName(file);
并且对于任意的message和service都可以根据其名称,通过DescriptorPool对应的Descriptor和ServiceDescriptor。
1.2 只使用proto文件,不使用protoc进行编译
这种情况需要手动解析proto文件,再获取FileDescriptor。protobuf提供了响应的解析器compiler,通过compoiler可以方便的获取proto文件的FileDescriptor
const FileDescriptor* GetFileDescriptorFromProtoFile(const std::string &protoRoot, const std::string &protoFile){ compiler::DeskSourceTree sourceTree; sourceTree.MapPath("", protoRoot); FileErrorCollector errorCollector; compiler::Importer importer(&sourceTree, &errorCollector); return importer.Import(protoFile); }
2. 调用message的属性和方法
首先获取message的对象:
const Message* prototype = MessageFactory::generated_factory()->GetPrototype(descriptor); if(!prototype){ continue; } Message *message = prototype->New();
有了message对象,因为所有的message都是Message* 类型的,但不同的message对象的属相和方法是不一样的,所以并不能直接调用其对象和方法。
protobuf是通过Reflection来调用message的属性和方法的。message中的方法只有对各个属性的get和set,而调用message的属性其实也就是调用属性的get。调用message的某一个属性的get和set,就需要该属性的Descriptor,通过Reflection将相应的值写入到message对应的属性:
const FieldDescriptor * field_descriptor = message_descriptor->field(i); if(field_descriptor->type() == FieldDescriptor::TYPE_INT32){ reflection->SetInt32(message, field_descriptor, 0); }
3.Protobuf反射使用案例
这部分主要是重复上两节的内容,进行完整示例。以下是使用的test.proto文件
package T; message Test { optional int32 id = 1; }
3.1通过类型名字创建出类型对象
预编译好proto模式
//! 利用类型名字构造对象. /*! * @Param type_name 类型名字,比如 "Test.TestMessage". * @Return 对象指针,new 出来的,使用者负责释放. */ #include <google/protobuf/descriptor.h> #include <google/protobuf/message.h> #include "cpp/test.pb.h" // 这是protoc给你生成的文件 int main() { // 先获得类型的Descriptor . auto descriptor = google::protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName("T.Test"); if (nullptr == descriptor) { return 0 ; } // 利用Descriptor拿到类型注册的instance. 这个是不可修改的. auto prototype = google::protobuf::MessageFactory::generated_factory()->GetPrototype(descriptor); if ( nullptr == descriptor) { return 0 ; } // 构造一个可用的消息. auto message = prototype->New(); // 只有当我们预先编译了test消息并且正确链接才能这么干. auto test = dynamic_cast<T::Test*>(message); // 直接调用message的具体接口 // 其实这些接口是语法糖接口.所以并没有对应的反射机制来对应调用. // 反射机制实现了的Set/Get XXX系列接口,是属于Reflection的接口,接收Message作为参数. test->set_id(1); std::cout<<test->Utf8DebugString()<<std::endl; delete message ; return 0 ; }
直接解析proto文件模式
#include <iostream> #include <google/protobuf/compiler/importer.h> #include <google/protobuf/dynamic_message.h> int main() { // 准备配置好文件系统 google::protobuf::compiler::DiskSourceTree sourceTree; // 将当前路径映射为项目根目录 , project_root 仅仅是个名字,你可以你想要的合法名字. sourceTree.MapPath("project_root","./"); // 配置动态编译器. google::protobuf::compiler::Importer importer(&sourceTree, NULL); // 动态编译proto源文件。 源文件在./source/proto/test.proto . importer.Import("project_root/source_proto/test.proto"); // 现在可以从编译器中提取类型的描述信息. auto descriptor1 = importer.pool()->FindMessageTypeByName("T.Test"); // 创建一个动态的消息工厂. google::protobuf::DynamicMessageFactory factory; // 从消息工厂中创建出一个类型原型. auto proto1 = factory.GetPrototype(descriptor1); // 构造一个可用的消息. auto message1= proto1->New(); // 下面是通过反射接口给字段赋值. auto reflection1 = message1->GetReflection(); auto filed1 = descriptor1->FindFieldByName("id"); reflection1->SetInt32(message1,filed1,1); // 打印看看 std::cout << message1->DebugString(); // 删除消息. delete message1 ; return 0 ; }
3.2 通过对象和对象的属性获取名字,修改对应属性
#include "cpp/test.pb.h" #include <iostream> int main() { // 拿到一个对象,不在乎怎么拿到,可以是通过反射拿到。 // 这里简单直接的创建一个. T::Test p_test ; // 拿到对象的描述包. auto descriptor = p_test.GetDescriptor() ; // 拿到对象的反射配置. auto reflecter = p_test.GetReflection() ; // 拿到属性的描述包. auto field = descriptor->FindFieldByName("id"); // 设置属性的值. reflecter->SetInt32(&p_test , field , 5 ) ; // 获取属性的值. std::cout<<reflecter->GetInt32(p_test , field)<< std::endl ; return 0 ; }
4. 处理Protobuf反射,通过协议字串动态生成协议
下面两部分是针对protobuf通讯协议的内容。下面这两个函数可以动态生成pb的message,其中ProtoMsg是pb package的名字
inline google::protobuf::Message* CreateMessage(const std::string& msg) { google::protobuf::Message* message = NULL; const google::protobuf::Descriptor* descriptor = google::protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName("ProtoMsg." + msg); if (descriptor) { const google::protobuf::Message* prototype = google::protobuf::MessageFactory::generated_factory()->GetPrototype(descriptor); if (prototype) { message = prototype->New(); } } return message; } inline void ReleaseMessage(google::protobuf::Message* pMsg) { if (NULL != pMsg) { pMsg->Clear(); delete pMsg; } }
5. 通过反射将配置中的值设置进pb字段
可以通过如下方法,自动创建message并对field赋值:
// mstrCurMsg 当前正在执行的协议 google::protobuf::Message* pMsg = Test::CreateMessage(mstrCurMsg); const google::protobuf::Descriptor* pDescriptor = google::protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName("ProtoMsg." + mstrCurMsg); assert(NULL != pDescriptor); // 这一个可以获取到反射类,然后可以将配置中值赋值进去 const google::protobuf::Reflection* pReflection = pMsg->GetReflection(); assert(NULL != pReflection); for (int i = 0; i < pDescriptor->field_count(); ++i) { const google::protobuf::FieldDescriptor* pFieldDescriptor = pDescriptor->field(i); assert(NULL != pFieldDescriptor); const std::string& strFieldName = pFieldDescriptor->name(); const TestConfigModule::MsgEntry* pMsgEntry = pMsgStruct->GetMsgEntry(strFieldName); assert(NULL != pMsgEntry); // 读取字段类型,顺带可以做类型检查 assert(pMsgEntry->mnType == pFieldDescriptor->type()); // 设置值 switch (pMsgEntry->mnType) { case Test::TYPE_STRING: pReflection->SetString(pMsg, pFieldDescriptor, pMsgEntry->mstrValue); break; // ... default: break; } } std::string strData; if (!pMsg->SerializeToString(&strData)) { m_pLogModule->LogNormal("Test stop, cannot SerializeToString ", mstrCurMsg, __FUNCTION__, __LINE__); return; } Test::ReleaseMessage(pMsg);
6. 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 field_count(); ++i) { const google::protobuf::FieldDescriptor* field = descriptor->field(i); bool has_field = reflection->HasField(message, field); if (has_field) { //arrays not supported assert(!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(&wsize), sizeof(wsize)); serialized_string->append(field->name().c_str(), field->name().size()); wsize = sizeof(value); serialized_string->append(reinterpret_cast(&wsize), sizeof(wsize)); serialized_string->append(reinterpret_cast(&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_TYPE case google::protobuf::FieldDescriptor::CPPTYPE_ENUM: { int value = reflection->GetEnum(message, field)->number(); int wsize = field->name().size(); //写入name占用字节数 serialized_string->append(reinterpret_cast(&wsize), sizeof(wsize)); //写入name serialized_string->append(field->name().c_str(), field->name().size()); wsize = sizeof(value); //写入value占用字节数 serialized_string->append(reinterpret_cast(&wsize), sizeof(wsize)); //写入value serialized_string->append(reinterpret_cast(&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(&wsize), sizeof(wsize)); serialized_string->append(field->name().c_str(), field->name().size()); wsize = value.size(); serialized_string->append(reinterpret_cast(&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(&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(&wsize), sizeof(wsize)); serialized_string->append(value.c_str(), value.size()); break; } } } } }
7. 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 field_map; for (int i = 0; i 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.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(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::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(value.c_str()))); std::cout name() (value.c_str())) enum_type()->FindValueByNumber(*(reinterpret_cast(value.c_str()))); reflection->SetEnum(message, field, enum_value_descriptor); std::cout name() (value.c_str())) SetString(message, field, value); std::cout name() MutableMessage(message, field); parse_message(value, submessage); break; } default: { break; } } } }
只是基本介绍下,在遇到问题时候还是多求助官方文档的。
《Protobuf反射详解》有1条评论