IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    深入挖掘protobuf: 通过protoc获取proto文件信息

    战魂小筑发表于 2015-03-01 05:49:00
    love 0

    准备:

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

    战魂小筑 2015-03-01 13:49 发表评论



沪ICP备19023445号-2号
友情链接