首页 > 解决方案 > ProtoBuf:如何为新消息重用 FieldDescriptor?如何有效地从动态消息中获取字段值?

问题描述

我目前正在从事一个项目,我想在其中使用 Google 协议缓冲区 (C++) 来序列化和反序列化数据。该应用程序具有以下要求:

  1. 一项要求是不使用从文件生成预编译 C++ 类的标准方法.proto(使用 protoc)。相反,我使用. 在运行时google::protobuf::compiler::Importer导入我的.proto文件,然后google::protobuf::Message使用google::protobuf::DynamicMessageFactory. 使用这种方法,我已经可以序列化/反序列化字节数组/消息,而无需事先生成 C++ 类。
  2. 性能:我预计大约每 50 毫秒就有一次新的字节流,因此解析字节数组并从动态创建的消息中读取值必须非常有效。对于解析步骤,我只是使用标准ParseFromArray(…)方法来获取我的消息。目前,我认为不需要优化这一步。相反,我目前正在寻找一种更有效地检索值的方法。

我知道 1. 和 2. 有点矛盾,因为使用DynamicMessageFactory可能比生成预编译的 C++ 类更昂贵,但不幸的是 1. 是硬性要求并且无法更改。

要检索我当前正在使用的已解析消息的值google::protobuf::Reflectiongoogle::protobuf::Descriptor遍历我的消息,直到google::protobuf::FieldDescriptor找到对应的消息。由于遍历消息的效率非常低,尤其是当一条消息预计有大约 1000 个字段时,我想到了FieldDescriptor在地图中缓存找到的内容,然后将缓存FieldDescriptor的 's 用于其他消息,而无需再次遍历每条消息(因为我所有的消息具有相同的结构)。这种方法是在这里提出的。不幸的是,我没能让它工作(见下面我的简化示例代码)。你们能帮帮我吗?提前感谢您的任何建议。

int main()
{
  MyUtil util; // My protobuf utility class

  int size;
  uint8_t* data = util.GenerateByteArray(&size); // Generate sample byte array

  Message* message1 = util.ParseFromArray(data, size);
  util.SetDouble(message1, "position.x", 1.111);

  Message* message2 = util.ParseFromArray(data, size);
  util.SetDouble(message2, "position.x", 2.222);

  cout << util.GetDouble(message1, "position.x") << endl;
  cout << util.GetDouble(message2, "position.x") << endl;

  // This works as expected if my GetDouble method iterates through the whole message for every new message.
  // But if I try to cache my FieldDescriptors I get the wrong output (see below).

  return 0;
}

class MyUtil
{
private:
  // The cached descriptors
  const Message* mTempMessage = nullptr;
  const FieldDescriptor* mTempField = nullptr;

public:
  MyUtil() { /*Initializing and importing .proto files*/ }

  // ... Some other methods

  double GetDouble(const Message* message, const string& path)
  {
    if (mTempField)
    {
      // Problem: This doesn't work if I only pass the actual 'message2' so I attempted to also cache mTempMessage.
      // But mTempMessage will always refer to the original message1 from which mTempField was retrieved.
      // This is why util.GetDouble(message2, "position.x") will always only return the value of message1.
      // So I know why my output is wrong but I don't know how the correct solution should look like.
      return mTempMessage->GetReflection()->GetDouble(*mTempMessage, mTempField);
    }

    vector<string> fieldNames;
    boost::split(fieldNames, path, boost::is_any_of(".")); // "position.x" => ["position", "x"]

    vector<string>::iterator iterator = fieldNames.begin();
    vector<string>::iterator last = fieldNames.end() - 1;

    return GetDouble(message, &iterator, &last);
  }

  double GetDouble(const Message* message, vector<string>::iterator* pathIterator, vector<string>::iterator* pathLast)
  {
    // Get FieldDescriptor using message->GetDescriptor()->FindFieldByName(...)
    const FieldDescriptor* field = GetField(message, **pathIterator);

    if (*pathIterator != *pathLast)
    {
      // Field "position": Is another message, so call recursively for next field
      (*pathIterator)++;

      // Get the inner "position" message using Reflection
      const Message* messageField = GetMessageField(message, field);

      return GetDouble(messageField, pathIterator, pathLast);
    }
    else if (field->type() == FieldDescriptor::TYPE_DOUBLE)
    {
      // Field "x": Actual value can be retrieved

      // Caching the found FieldDescriptor (and also the current "position" message)
      mTempMessage = message;
      mTempField = field;

      return message->GetReflection()->GetDouble(*message, field);
    }
    else
    {
      // Return some invalid value
    }
  }
};

标签: c++visual-c++reflectionprotocol-buffersprotobuf-c

解决方案


推荐阅读