本文是Protobuf第三方扩展开发指南的一个章节,由于不推荐采取本方案,因此把本章节单独抽取出来节约原文篇幅。
2.1 方案一:通过C++接口实现编译器后端(不推荐)
早期Protobuf 2不支持插件,因此一些较老的开源项目是使用此方案:
- protobuf-c
2.1.1 项目需求介绍
本节实现一个简易的Protobuf三方扩展,命名为protoc-gen-hi。输入是proto文件,输出是一些“hello”。
例如对于这个proto输入:
syntax = "proto3";
package demo;
message Person {
string name = 1;
int32 id = 2;
string email = 3;
}
protoc-gen-hi对应的输出是:
hello Person
hello name
hello id
hello email
2.1.2 开发环境搭建
在Release页面下载Protobuf源码:protobuf-all-3.6.1.tar.gz
解压:tar xf ./protobuf-all-3.6.1.tar.gz
编译:
cd ./protobuf-all-3.6.1
-
sh ./autogen.sh
,可能会因为没有装libtool
报错 -
./configure
,可能会因为没有安装g++
报错 -
make
,需要等挺久的
编译主要是希望得到这些文件:
- ./src/.libs/libprotoc.so
- ./src/.libs/libprotobuf.so
- ./src/.libs/libprotoc.a
- ./src/.libs/libprotobuf.a
创建工作目录:mkdir lab1
,创建完成后本地目录应该是这样的:
.
|-- lab1
|-- protobuf-3.6.1
| |-- src
| | |-- .libs
| | | |-- libprotobuf.a
| | | |-- libprotobuf.so
| | | |-- libprotoc.a
| | | |-- libprotoc.so
| | | `-- protoc
| | `-- google
| `-- Makefile
`-- protobuf-all-3.6.1.tar.gz
2.1.3 开始写代码
现在我们开始编写第一个编译器。在lab1目录下创建几个文件,完成后工作区应该是这样的:
.
|-- lab1
| |-- test
| | `-- person.proto
| |-- Makefile
| |-- main.cc
| |-- hi_generator.cc
| `-- hi_generator.h
|-- protobuf-3.6.1
`-- protobuf-all-3.6.1.tar.gz
其中main.cc关键代码如下:
// 将我们的编译器关联到cli
// 参考谷歌官方文档:
// https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.compiler.command_line_interface
int main(int argc, char* argv[]) {
google::protobuf::compiler::CommandLineInterface cli;
// 官方实现的C++编译器后端
google::protobuf::compiler::cpp::CppGenerator cpp_generator;
cli.RegisterGenerator("--cpp_out", &cpp_generator, "Generate C++ source and header.");
// 我们需要实现的编译器后端
google::protobuf::compiler::c::HiGenerator hi_generator;
cli.RegisterGenerator("--hi_out", &hi_generator, "Generate C header.");
return cli.Run(argc, argv);
}
hi_generator.cc关键代码如下,读者只需要关注FileDescriptor
参数和io::Printer
变量:
// FileDescriptor就是语法树,参考:
// https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.descriptor
bool HiGenerator::Generate(const FileDescriptor* file,
const string& parameter,
GeneratorContext* context,
string* error) const {
string filename = file->name();
string output_filename = ReplaceSuffix(filename, ".proto", ".txt"); // 文件名proto后缀替换为txt,这样xxx.proto的生成物就是xxx.txt
std::unique_ptr<io::ZeroCopyOutputStream> output(context->Open(output_filename));
GOOGLE_CHECK(output.get());
io::Printer printer(output.get(), '$'); // 指定$符号作为模板变量的定界符(delimitation)
printer.Print("Hello $file$!\n", "file", filename); // file是一个模板变量,file变量的值是`filename'
// 假设filename变量现在是"person.proto",那么这里会被替换为"Hello person.proto!\n"
for (int i = 0; i < file->message_type_count(); i++) {
const Descriptor *message = file->message_type(i);
printer.Print("Hello $message$!\n", "message", message->full_name());
}
return true;
}
第一个编译器写好了,完整代码见:https://github.com/chend0316/protoc-lab/tree/master/lab1
2.1.4 编译、运行
通过make
编译完成后,我们运行一下:./myprotoc --c_out=./test ./test/person.proto
运行后可以看到生成了这个文件./test/test/person.txt,打开来看看效果。