战魂小筑

讨论群:309800774 知乎关注:http://zhihu.com/people/sunicdavy 开源项目:https://github.com/davyxu

   :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  257 随笔 :: 0 文章 :: 506 评论 :: 0 Trackbacks

准备:

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工程内有详细代码解析

posted on 2015-03-01 13:49 战魂小筑 阅读(10732) 评论(0)  编辑 收藏 引用 所属分类: 脚本技术工具使用及设计

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理