准备:
在http://code.google.com/p/protobuf/下载protobuf-2.5版本
预备知识: 已经使用过protobuf, 熟练应用protobuf序列化在各语言间交互信息
目标: 获取proto内容而无需手动解析proto文件
为proto文件添加更多的meta信息, 并在运行期获取.
protoc编译器准备
通过protobuf-2.5的源码或者从官网下载, 可以获得protoc的protobuf编译器, 这个编译器由C++编写, 官方支持完整的protobuf特性. 编译器默认支持C++, python和java 三种语言的代码生成. 如需生成更多的语言, 可以通过官网的第三方页面获取.
protoc插件原理
但我们在日常使用中, 可能需要提取proto信息, 例如: 所有的枚举,消息等信息, 字段名称和导出号. 自己编写词法解析器来做是费力不讨好的. 官方推荐的方法是使用protoc外挂插件来实现.
protoc的插件设计比较独特, 不使用动态链接库或者java的jar包导入方式, 而是直接使用了命令行来交换数据.查看protobuf源码我们可以发现这样一个文件:
protobuf-2.5.0\src\google\protobuf\descriptor.proto
这个文件描述了一个proto文件的格式, 消息组成及枚举等完整信息. 这是一种自我描述的方法.
在找到这样一个文件
protobuf-2.5.0\src\google\protobuf\compiler\plugin.proto
这样一个文件描述: 插件如何与protoc进行交互的协议
protoc编译器在给定指定proto文件及搜索路径后, 将各种信息填充为descriptor.proto描述的结构后通过CodeGeneratorRequest消息系列化为二进制流后输出到命令行. 插件只用捕获protoc命令行输出的二进制流, 序列化化回CodeGeneratorRequest即可获得解析后的proto文件内容
这里需要注意的是: 插件可执行文件很有讲究, 必须为protoc-gen-$NAME, 而且输出文件名参数必须为--${NAME}_out
看一个栗子:
protoc.exe foo.proto --plugin=protoc-gen-go=..\tools\protoc-gen-go.exe --go_out foo.go --proto_path "."
这个栗子里: $NAME=go
protoc将foo.proto文件(搜索路径为当前路径)的内容通过命令行输出给位于..\tools\的插件protoc-gen-go.exe, 输出文件名字为 foo.go
descriptor.proto信息挖掘
我们注意到在descriptor.proto文件中包含有这样的一个message: SourceCodeInfo, 这个消息体里有如下字段
optional string leading_comments = 3;
optional string trailing_comments = 4;
这两个字段对于我们获取proto文件的meta信息尤为重要, 所谓的meta信息, 理解理解为C#语言中的attribute
这个attribute功能可以为一个字段, 一个消息扩充一些描述. 比如: 当一个字段通过反射显示在gui上时, gui需要获取这个字段的中文描述
那么只需要如下编写
optional int32 somevalue = 1 //@ desc=”中文描述”
位于字段尾部的描述, 会被填充到SourceCodeInfo的 trailing_comments中, 而位于字段上方的字段, 会被填充到leading_comments中
SourceCodeInfo 并没有直接挂载在message或者字段的附近, 而是通过其下的path字段来描述与字段的关系, 这是个极为麻烦的设计.
其原理如下:
假设我有如下一个message
message foo
{
optional int32 v = 1; // comments
}
要获取v后的注释, 对应的path为 4, 0, 2, 0
4 表示descriptor中message_type所在的序号,由于message_type对应的类型DescriptorProto是一个数组, 所以0表示foo是在FileDescriptorProto的message_type数组类型的索引为0;
如此类推: 2, 0 表示 v在DescriptorProto结构体的field成员序号为2的数组元素的索引为0
如果需要更多的参考, 可以获取https://github.com/golang/protobuf
github.com\golang\protobuf\protoc-gen-go工程内有详细代码解析