我目前正在重新访问一个使用Google Protocol Buffers的项目。
在这个项目中,我想利用协议缓冲区的描述符和反射的特性。
官方文档指出.proto
文件的注释可以读取:
- 使用
DebugStringWithOptions()
函数,在消息或描述符上调用。 - 带有
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描述符有了更好的理解。
如果下列一个或多个语句不正确,请纠正我:
-
SelfDescribingMessage
proto只有在对端不知道.proto定义时才有用。 - 访问原型定义注释的唯一方法是使用
protoc
应用程序创建一个.desc文件。 - 要获得注释,GetSourceLocation成员函数只能在"top"元素为
FileDescriptorSet
、FileDescriptorProto
或FileDesriptor
时使用。如果这是正确的,协议缓冲区有一个糟糕的API设计,因为google::protobuf::Message
类是一个上帝类(提供对完整的文件描述符API的访问,但根本没有提供值)。 - 调用
concrete_message.descriptor()->file()
不(和不能)包含源注释信息,因为它不是编译代码的一部分。
在我看来,唯一可行的办法是:
消息的调用协议。Proto文件(引用所有其他消息),参数为:
--include_imports --include_source_info and --descriptor_set_out=message.desc
将
message.desc
文件与应用程序/库一起发布,以便能够在运行时读取它(见下文)。 创建- 遍历
FileDescriptorSet
的所有google::protobuf::FileDescriptorProto
。 - 使用
google::protobuf::DescriptorPool::BuildFile()
将每个FileDescriptorProto转换为google::protobuf::FileDescriptor
。 - 使用
Find…
函数之一查找消息和/或字段,应用于FileDescriptor
实例。 - 在消息/字段描述符实例上调用
GetSourceLocation
函数 - 通过
google::protobuf::SourceLocation::leading_comments
和google::protobuf::SourceLocation::trailing_comments
阅读评论。
google::protobuf::FileDescriptorSet
这对我来说似乎很复杂,所以我有两个额外的问题:
- 有没有一种方法来包括源信息,而不使用FileDescriptorSet?
- 是否有可能"连接"/设置
FileDescriptorSet
与具体的消息类/实例,因为这会大大简化事情?
编辑2015-09-25:通过神类我的意思是Message
类和/或描述符类提供或多或少无用的公共函数,因为它们在客户端使用时不提供任何信息。以"正常"消息为例:因此生成的代码不包含源注释信息,因此所有描述符类(例如Descriptor
和FieldDescriptor
)中的GetSourceLocation
方法完全无用。从逻辑的角度来看,如果处理消息,应该提供单独的实例DescriptorLite
和FieldDescriptorLite
,如果处理来自FileDescriptorSet
(其源通常是由.proto文件生成的.desc文件)的信息,应该提供Descriptor
和FieldDescriptor
。然后,[...]Lite
类将成为"正常"类的父类。protoc
可能永远不会包含源代码注释的论点强调了我的观点。
通过"连接",我的意思是一个API函数用来自.desc文件的描述符信息更新消息中的描述符信息(如果我理解正确的话,它始终是消息提供的描述符的超集)。
听起来你基本上已经想通了。
你正在深入了解协议编译器中的api,这些api并不是真正为公共消费而设计的。它变得复杂,因为没有人写一个帮助层来简化事情,因为没有多少人使用这些功能。
我不知道你说的Message
是"神类"是什么意思。Message
只是一个protobuf实例的抽象接口。描述符描述protobuf实例的类型。Message::getDescriptor()
返回消息的类型,但除此之外,这些api之间没有太多的直接连接…
有没有一种方法来包含源信息而不使用FileDescriptorSet?
注释被有意地从嵌入到生成代码中的描述符中剥离,因此您需要单独运行解析器,生成一个描述符集,并动态地使用它。
是否有可能"连接"/设置FileDescriptorSet与具体的消息类/实例,因为这会大大简化事情?
您的意思是希望Message::getDescriptor()返回一个包含源文件注释数据的描述符吗?这将需要将注释数据嵌入到生成的代码中,这对于protoc
来说是微不足道的实现(它目前有意将它们剥离,所以它只需要而不是这样做),但可能会臃肿和危险(可能会泄露使用protobufs构建的闭源二进制文件的人的秘密)。