4

Reflection in protobuf (C++/Java)

 2 years ago
source link: https://forrestsu.github.io/posts/library/reflection-in-protobuf-cpp-java/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

最近工作中,需要做一些消息动态解析,因为使用的 protobuf,考虑使用protobuf的反射特性。

1 reflection in C++

在c++中使用protobuf 反射

package com.sunquan;

message Login 
{
    optional int64 userid = 1;
    optional string username = 2; // name
    optional string password = 3; // passwd
    optional string email = 4;
    optional string nickname = 5;
    // etc ... 
}

  C++和Java 不同的是: c++有一个全局的pool,管理了所有定义在 proto 文件里的消息原型, 我们可以通过消息全称,查找到对应的单例的消息原型,然后通过原型构造可变的消息。

/**
 * 通过消息名称, 获取构造该类型的默认(原型)。 然后你可以调用该消息的 New() 方法来构造这种类型的可变消息。
 * @param FullMsgName  eg: com.sunquan.Login
 * @return if fail return NULL
 */
const google::protobuf::Message* ParseUtil::FindMsgProtoType(const std::string& FullMsgName)
{
    google::protobuf::Message* pFactory = NULL;
    auto pDesc = google::protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName(FullMsgName);
    if (pDesc)
    {
        // Calling this method twice with the same Descriptor returns the same object.
        pFactory = google::protobuf::MessageFactory::generated_factory()->GetPrototype(pDesc);
    } else
        std::cerr << "can't find new desc:" << FullMsgName << std::endl;
    return pFactory;
}

当我们通过消息原型new一个可变的消息对象,就可以对消息进行动态赋值了,如下:

int ParseUtil::ReflectionTest() {
    std::string fullname = "com.sunquan.Login";
    const google::protobuf::Message* pMsgFactory = FindMsgProtoType(fullname);
    if (pMsgFactory == NULL){
       std::cerr << "CreateMessage() fail!" <<std::endl;
       return 0;
    }
    //new mutable msg
    google::protobuf::Message* pMsg = pMsgFactory->New();

    const google::protobuf::Descriptor *pdesc = pMsgFactory->GetDescriptor();
    const google::protobuf::Reflection *refl = pMsgFactory->GetReflection();

    // 动态查找一个 FieldDesc
    std::string fieldname = "username";
    const google::protobuf::FieldDescriptor* pfield = pdesc->FindFieldByName(fieldname);
    if (pfield) {
        // 暂不支持 repeat 类型
        if (pfield->is_repeated()) {
            std::cerr << "repeat field is not support!" << fieldname << std::endl;
            continue;
        }
        switch (pfield->cpp_type()) {
        case google::protobuf::FieldDescriptor::CppType::CPPTYPE_INT32:
            //if(oneRow[i].type() == QVariant::Date)
            refl->SetInt32(pMsg, pfield, 100);
            break;
        case google::protobuf::FieldDescriptor::CppType::CPPTYPE_INT64:
            refl->SetInt64(pMsg, pfield, 100LL);
            break;
        case google::protobuf::FieldDescriptor::CppType::CPPTYPE_UINT32:
            refl->SetUInt32(pMsg, pfield, 100);
            break;
        case google::protobuf::FieldDescriptor::CppType::CPPTYPE_UINT64:
            refl->SetUInt64(pMsg, pfield, 100ULL);
            break;
        case google::protobuf::FieldDescriptor::CppType::CPPTYPE_DOUBLE:
            refl->SetDouble(pMsg, pfield, 100.89);
            break;
        case google::protobuf::FieldDescriptor::CppType::CPPTYPE_FLOAT:
            refl->SetFloat(pMsg, pfield, 100.98);
            break;
        case google::protobuf::FieldDescriptor::CppType::CPPTYPE_BOOL:
            refl->SetBool(pMsg, pfield, true);
            break;
        case google::protobuf::FieldDescriptor::CppType::CPPTYPE_STRING:
            refl->SetString(pMsg, pfield, "name");
            break;
        default:
            //NoSupport CPPTYPE_ENUM/CPPTYPE_MESSAGE
            std::cout << "UnSupport Type=" << pfield->type_name() << std::endl;
            break;
        }
    }
    //if no longer use, please remember delete "pMsg"
    if (pMsg) delete pMsg;
    return 0;
}

2 reflection in Java

在Java中使用的protobuf反射。 1 已知消息名称,查找到该消息类型的 Descriptor,然后根据序列化的字节码,动态构建对象。


	public static void Reflect_Parse()
	{
		Login req = Login.newBuilder()
		        .setUserid(100)
		        .setUsername("Alice")
		        .setEmail("[email protected]")
		        .setNickname("爱丽丝")
	    		.setPassword(Digest.getStringMD5("A123456"))
	    		.build();
		ByteString data = req.toByteString();
		
		try {
		    //根据消息名称查找 Descriptor, DynamicMessage工厂 根据指定的 Descriptor 产生消息 object
            String magname = "Login";
            Descriptor desc = DemoProtos.getDescriptor().findMessageTypeByName(magname);

            if (desc != null) {
                // com.sunquan.zmqproto.Login
                System.out.println(desc.getFullName());
                DynamicMessage msg = DynamicMessage.parseFrom(desc, data);
                System.out.println(msg.toString());
            } else {
                System.out.println("Can't find msg desc: " + magname);
            }
            //底层的实现: 每个消息自身都实现了 自己的  getDescriptor() 接口
            Descriptor desc_direct = Login.getDescriptor();
		} catch (InvalidProtocolBufferException e) {
			e.printStackTrace();
		}
	}

2 预先准备好所有可能的消息,动态解析 把所有可能的消息的 Descriptor 加入到 Map,然后从Map中查找。

   private Map<MsgType, Descriptor> typeMap = new TreeMap<MsgType, Descriptor>();
   typeMap.put(MsgType.LOGINREP, LoginRep.getDescriptor());
   typeMap.put(MsgType.INSERTORDERREP, InsertOrderRep.getDescriptor());
   typeMap.put(MsgType.CANCELORDERREP, CancelOrderRep.getDescriptor());

3 动态修改消息字段值 wait


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK