protobuf的进阶技能之反射


之前我们介绍了protobuf的简单使用。现在,我们踏上进阶之路。

    这里要用到的技术就是反射,当然,这个是protocol buffer自己提供的,无需我们去实现,我们要做到,只是使用。可能有同学会好奇,反射有什么作用呢?为什么它就是进阶的技能?

    设想一个场景,你要将一个protocol buffer类型的数据,转换成json格式的文件,至于为什么是json,可能是因为通过看json的文件会更直观点。这时候我们该怎么做?首先我们要思考,我们需要哪些信息?json是需要key的,而这个key,对应proto文件,就是字段的名称。另外一个,就是值。而且,我们要使程序能应用于任意的一个protocol buffer数据。

    要解决上面的问题,就需要用到反射。通过protobuf的数据,反射出它的字段,字段类型,字段值等等。反射在protobuf中对应的是class Reflection.它提供以下几个关键的方法:

virtual bool HasField(const Message & message, const FieldDescriptor * field) //判断字段是否存在
virtual int FieldSize(const Message & message, const FieldDescriptor * field) //获取repeated类型字段的元素个数
Get*(const Message & message, const FieldDescriptor * field) //用于获取非repeated类型字段, *表示类型
Set*(Message * message, const FieldDescriptor * field, T value) //用于设置非repeated类型字段  *、T表示类型
GetRepeated*(const Message & message, const FieldDescriptor * field, int inde);  //index表示下标,用于repeated类型字段。 *、T表示类型
SetRepeated*(Message * message, const FieldDescriptor * field, int index, T value)  //设置指定下标处的值,用于repeated类型字段。*、T表示类型
Add*(Message * message, const FieldDescriptor * field, T value); //将值插入到字段中,用于repeated类型字段。  *、T表示类型

如:

AddInt32、SetRepeatedInt32、GetRepeatedInt32、SetInt32、GetInt32等方法。

看到上面函数的参数,发现都需要FieldDescriptor类型,这是一个描述字段的类型,用于存储protobuf的消息中的字段。类中有如下几个关键的方法和类型:

const string & name() const  //字段名称
Type	type() const  //字段类型
CppType	cpp_type() const  //对应的cpp类型
bool	is_required() const  //是否required
bool	is_optional() const //是否optional
bool	is_repeated() const  //是否repeated

enum FieldDescriptor::Type {
  TYPE_DOUBLE = = 1,
  TYPE_FLOAT = = 2,
  TYPE_INT64 = = 3,
  TYPE_UINT64 = = 4,
  TYPE_INT32 = = 5,
  TYPE_FIXED64 = = 6,
  TYPE_FIXED32 = = 7,
  TYPE_BOOL = = 8,
  TYPE_STRING = = 9,
  TYPE_GROUP = = 10,
  TYPE_MESSAGE = = 11,
  TYPE_BYTES = = 12,
  TYPE_UINT32 = = 13,
  TYPE_ENUM = = 14,
  TYPE_SFIXED32 = = 15,
  TYPE_SFIXED64 = = 16,
  TYPE_SINT32 = = 17,
  TYPE_SINT64 = = 18,
  MAX_TYPE = = 18
}


enum FieldDescriptor::CppType {
  CPPTYPE_INT32 = = 1,
  CPPTYPE_INT64 = = 2,
  CPPTYPE_UINT32 = = 3,
  CPPTYPE_UINT64 = = 4,
  CPPTYPE_DOUBLE = = 5,
  CPPTYPE_FLOAT = = 6,
  CPPTYPE_BOOL = = 7,
  CPPTYPE_ENUM = = 8,
  CPPTYPE_STRING = = 9,
  CPPTYPE_MESSAGE = = 10,
  MAX_CPPTYPE = = 10
}

以上API接口,来自于:https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.message#Reflection    

https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.descriptor#FieldDescriptor

介绍到这里,基本上就可以获取到我们需要的信息了。先来看份示例代码:

test.proto

package test;

message person
{
        required int32 id               = 1;
        required string name    =2;
        required int32 age              =3;
}

message classCPP
{
        required uint32 classid = 1;
        repeated person students = 2;
}
test.cpp
#include "jsoncpp/json.h"

#include <google/protobuf/message.h>
#include <google/protobuf/repeated_field.h>
#include <google/protobuf/descriptor.h>

using google::protobuf::Message;
using google::protobuf::Descriptor;
using google::protobuf::FieldDescriptor;
using google::protobuf::Reflection;

int proto2json(const google::protobuf::Message * msg, Json::Value & data)
{
        //how ?
        //先获得proto内的数据
        const Descriptor* des = msg->GetDescriptor();
        const Reflection * ref = msg->GetReflection();

        for(int i = 0; i < des->field_count(); ++i)
        {
                const FieldDescriptor* field = des->field(i);
                const string & key = field->name();

                if (field->is_repeated())
                {
                        data[key].resize(0);

                        for(int j = 0; j < ref->FieldSize(*msg, field); ++j)
                        {
                                Json::Value temp;

                                switch(field->cpp_type()) 
                                {
                                        case FieldDescriptor::CPPTYPE_INT32:
                                                //cout<<ref->GetRepeatedInt32(*msg, field, j)<<endl;
                                                temp = ref->GetRepeatedInt32(*msg, field, j);
                                                break;
                                        case FieldDescriptor::CPPTYPE_STRING:
                                                temp = ref->GetRepeatedString(*msg, field, j);
                                                break;
                                        case FieldDescriptor::CPPTYPE_MESSAGE:
                                                proto2json(&ref->GetRepeatedMessage(*msg, field, j), temp);
                                                break;
                                        default:
                                                break;
                                }

                                data[key].append(temp);
                        }
                }
                else
                {
                        switch(field->cpp_type()) 
                        {
                                case FieldDescriptor::CPPTYPE_INT32:
                                        //cout<<ref->GetInt32(*msg, field)<<endl;
                                        data[key] = ref->GetInt32(*msg, field);
                                        break;
                                case FieldDescriptor::CPPTYPE_STRING:
                                        //cout<<ref->GetString(*msg, field)<<endl;
                                        data[key] = ref->GetString(*msg, field);
                                        break;
                                case FieldDescriptor::CPPTYPE_MESSAGE:
                                        proto2json(&ref->GetMessage(*msg, field), data[key]);
                                        break;
                                default:
                                        break;
                        }
                }
        }
}

int main()
{
    test::classCPP clas1;
    clas1.set_classid(1);

    test::person * pers = clas1.add_students();
    pers->set_id(12);
    pers->set_name("city");
    pers->set_age(20);
    
    Json::Value data;

    proto2json(&clas1, data);
    Json::FastWriter writer;

    string content  = writer.write(data);

    cout<<content<<endl;
    return 0;
}

    示例输出结果如下:

    {"students":[{"age":20,"id":12,"name":"city"}],"test":12}

  结果很直观,我们一看就知道是什么内容。反射大致就介绍到这里了,剩下的同学们自己发挥。


评论

发表评论