使用Protocol Buffers描述符对象从.proto文件中读取注释



我目前正在重新访问一个使用Google Protocol Buffers的项目。

在这个项目中,我想利用协议缓冲区的描述符反射的特性。

官方文档指出.proto文件的注释可以读取:

  1. 使用DebugStringWithOptions()函数,在消息或描述符上调用。
  2. 带有GetSourceLocation()函数,在描述符上调用。

我无法检索评论,所以我认为我做了一些完全错误的事情,或者该功能还没有在协议缓冲区中完全实现。

下面是一些代码片段:

google::protobuf::DebugStringOptions options;
options.include_comments = true;
std::cout << "google::protobuf::Descriptor::DebugStringWithOptions(): "
          << message.descriptor()->DebugStringWithOptions(options) << std::endl
          << std::endl;
const google::protobuf::FieldDescriptor* field_descriptor{
    message.descriptor()->field(1)};
// TODO(wolters): Why doesn't this work?
google::protobuf::SourceLocation* source_location{
    new google::protobuf::SourceLocation};
field_descriptor->GetSourceLocation(source_location);
// if (field_descriptor->GetSourceLocation(source_location)) {
std::cout << "start_line: " << source_location->leading_comments
          << std::endl;
std::cout << "end_line: " << source_location->leading_comments << std::endl;
std::cout << "start_column: " << source_location->start_column << std::endl;
std::cout << "end_column: " << source_location->end_column << std::endl;
std::cout << "leading_comments: " << source_location->leading_comments
          << std::endl;
std::cout << "trailing_comments: " << source_location->trailing_comments
          << std::endl;
// }

我已经尝试使用以下两种语法在.proto文件的评论,但这些方法似乎都不起作用:

MessageHeader header = 1;  // The header of this `Message`.
/**
 * The header of this `Message`.
 */
MessageHeader header = 1;

我正在使用GCC 4.7.1(启用了c++ 11支持)和最新的协议缓冲区版本3.0.0-alpha-4.1。

有人可以引导我到正确的方向和/或提供我一个工作的例子吗?

编辑2015-09-24:

在阅读了官方文档中的自描述消息部分并测试了许多东西之后,我似乎对protobuf描述符有了更好的理解。

如果下列一个或多个语句不正确,请纠正我:

  1. SelfDescribingMessage proto只有在对端不知道.proto定义时才有用。
  2. 访问原型定义注释的唯一方法是使用protoc应用程序创建一个.desc文件。
  3. 要获得注释,GetSourceLocation成员函数只能在"top"元素为FileDescriptorSetFileDescriptorProtoFileDesriptor时使用。如果这是正确的,协议缓冲区有一个糟糕的API设计,因为google::protobuf::Message类是一个上帝类(提供对完整的文件描述符API的访问,但根本没有提供值)。
  4. 调用concrete_message.descriptor()->file()不(不能)包含源注释信息,因为它不是编译代码的一部分。

在我看来,唯一可行的办法是:

  1. 消息的调用协议。Proto文件(引用所有其他消息),参数为:

    --include_imports --include_source_info and --descriptor_set_out=message.desc
    
  2. message.desc文件与应用程序/库一起发布,以便能够在运行时读取它(见下文)。

  3. 创建google::protobuf::FileDescriptorSet
  4. 遍历FileDescriptorSet的所有google::protobuf::FileDescriptorProto
  5. 使用google::protobuf::DescriptorPool::BuildFile()将每个FileDescriptorProto转换为google::protobuf::FileDescriptor
  6. 使用Find…函数之一查找消息和/或字段,应用于FileDescriptor实例。
  7. 在消息/字段描述符实例上调用GetSourceLocation函数
  8. 通过google::protobuf::SourceLocation::leading_commentsgoogle::protobuf::SourceLocation::trailing_comments阅读评论。

这对我来说似乎很复杂,所以我有两个额外的问题:

  1. 有没有一种方法来包括源信息,而不使用FileDescriptorSet?
  2. 是否有可能"连接"/设置FileDescriptorSet与具体的消息类/实例,因为这会大大简化事情?

编辑2015-09-25:通过神类我的意思是Message类和/或描述符类提供或多或少无用的公共函数,因为它们在客户端使用时不提供任何信息。以"正常"消息为例:因此生成的代码包含源注释信息,因此所有描述符类(例如DescriptorFieldDescriptor)中的GetSourceLocation方法完全无用。从逻辑的角度来看,如果处理消息,应该提供单独的实例DescriptorLiteFieldDescriptorLite,如果处理来自FileDescriptorSet(其源通常是由.proto文件生成的.desc文件)的信息,应该提供DescriptorFieldDescriptor。然后,[...]Lite类将成为"正常"类的父类。protoc可能永远不会包含源代码注释的论点强调了我的观点。

通过"连接",我的意思是一个API函数用来自.desc文件的描述符信息更新消息中的描述符信息(如果我理解正确的话,它始终是消息提供的描述符的超集)。

听起来你基本上已经想通了。

你正在深入了解协议编译器中的api,这些api并不是真正为公共消费而设计的。它变得复杂,因为没有人写一个帮助层来简化事情,因为没有多少人使用这些功能。

我不知道你说的Message是"神类"是什么意思。Message只是一个protobuf实例的抽象接口。描述符描述protobuf实例的类型。Message::getDescriptor()返回消息的类型,但除此之外,这些api之间没有太多的直接连接…

有没有一种方法来包含源信息而不使用FileDescriptorSet?

注释被有意地从嵌入到生成代码中的描述符中剥离,因此您需要单独运行解析器,生成一个描述符集,并动态地使用它。

是否有可能"连接"/设置FileDescriptorSet与具体的消息类/实例,因为这会大大简化事情?

您的意思是希望Message::getDescriptor()返回一个包含源文件注释数据的描述符吗?这将需要将注释数据嵌入到生成的代码中,这对于protoc来说是微不足道的实现(它目前有意将它们剥离,所以它只需要而不是这样做),但可能会臃肿和危险(可能会泄露使用protobufs构建的闭源二进制文件的人的秘密)。

最新更新