C++ Protobuf:如何迭代消息的字段?

23

我刚接触protobuf并且遇到了一个简单的问题:我需要遍历消息的字段并检查它们的类型。如果类型是消息,我将对该消息进行递归相同操作。

例如,我有如下的消息:

package MyTool;

message Configuration {
    required GloablSettings         globalSettings  = 1;
    optional string                 option1         = 2;
    optional int32                  option2         = 3;
    optional bool                   option3         = 4;

}

message GloablSettings {
    required bool                   option1         = 1;
    required bool                   option2         = 2;
    required bool                   option3         = 3;
}

现在,要在C++中显式访问字段值,我可以这样做:

MyTool::Configuration config;
fstream input("config", ios::in | ios::binary);
config.ParseFromIstream(&input);

bool option1val = config.globalSettings().option1();
bool option2val = config.globalSettings().option2();

等等,这种方法在字段数量很大的情况下并不方便。

我是否可以通过迭代来获取字段的名称和类型?我知道有一些被称为描述符反射的东西,但是我在尝试中没有成功。如果可能的话,能否给我提供示例代码?

谢谢!


3
对于任何对Java版本感兴趣的人,这是链接: https://github.com/google/protobuf/blob/245224902a80d489389df192c0990ff70f2f3e4c/java/src/main/java/com/google/protobuf/TextFormat.java#L294 - Anupam Saini
2个回答

30

这个方法有点老了,但或许还会有人从中受益。以下是一种打印protobuf消息内容的方法:

 void Example::printMessageContents(std::shared_ptr<google::protobuf::Message> m)
 {
      const Descriptor *desc       = m->GetDescriptor();
      const Reflection *refl       = m->GetReflection();   
      int fieldCount= desc->field_count();
      fprintf(stderr, "The fullname of the message is %s \n", desc->full_name().c_str());
      for(int i=0;i<fieldCount;i++)
      {
        const FieldDescriptor *field = desc->field(i);
        fprintf(stderr, "The name of the %i th element is %s and the type is  %s \n",i,field->name().c_str(),field->type_name());
      }
 } 

您可以在FieldDescriptor Enum Values中找到从field->type获取的可能值。例如,对于消息类型,您需要检查类型是否等于FieldDescriptor :: TYPE_MESSAGE

此函数打印protobuf消息的所有“元数据”。但是,您需要分别检查每个值的类型,然后使用Reflection调用相应的getter函数。

因此,使用这个条件,我们可以提取出字符串:

 if(field->type() == FieldDescriptor::TYPE_STRING  && !field->is_repeated())
      {
            std::string g= refl->GetString(*m, field);
            fprintf(stderr, "The value is %s ",g.c_str());
      }

然而,字段可以是重复或非重复的,对于这两种字段类型使用不同的方法。因此,在此处使用检查来确保我们使用了正确的方法。对于重复的字段,例如字符串,我们有以下方法:

GetRepeatedString(const Message & message, const FieldDescriptor * field, int index)

因此,它考虑了重复字段的索引。

对于类型为Message的FieldDescriptor,在提供的函数中只会打印消息的名称,我们最好也打印其内容。

      if(field->type()==FieldDescriptor::TYPE_MESSAGE)
       {
         if(!field->is_repeated())  
         {
           const Message &mfield = refl->GetMessage(*m, field);      
           Message *mcopy = mfield.New();
           mcopy->CopyFrom(mfield);
           void *ptr = new std::shared_ptr<Message>(mcopy);
           std::shared_ptr<Message> *m =
           static_cast<std::shared_ptr<Message> *>(ptr);
           printMessageContents(*m);
          }
       }

最后,如果该字段重复出现,则必须调用反射中的 FieldSize 方法并迭代所有重复的字段。


11

文件一定已经发生了变化...你能把链接固定到特定的提交吗?今天,你所引用的那行代码是FORWARD_IMPL(PrintString, val);,这并没有什么帮助。 - Cuadue
1
请查找类TextFormat :: Printer。看起来现在在头文件中声明,在.cc文件中定义。https://github.com/google/protobuf/blob/master/src/google/protobuf/text_format.h#L181 - Kenton Varda

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接