Protobuf-Plugin机制

plugin机制

protobuf是一个支持plugin机制的序列化框架,除了protobuf自带的几种语言的CodeGenerator,用户可以按需实现自己的插件,来实现语言的拓展(比如protoc-gen-lua)或者功能的拓展(厂子内部的mcpack2pb插件)。

因为protoc插件需要实现跨平台、跨语言,所以采用的方式是父子进程的工作方式,父子进程通过pipe(父子进程共享fd)方式通信,父子进程通信数据的格式定义在compiler/plugin.proto。

  1. 父进程(protoc进程)中负责读取proto文件,转化为CodeGeneratorRequest格式,启动子进程,以及后续持久化子进程返回内容;
  2. 子进程(自定义插件进程)完成中子进程中启动自定义的CodeGeneratorResponse格式,按照自己需要完成处理,返回给父进程(protoc进程);

avatar

父子进程共享fd工作机制说明:

利用父子进程共享fd机制,建立pipe(单工模式)。

  1. 子进程一侧,做重定向,将stdin_pipe[0]重定向到STDIN_FILENOstdout_pipe[1]重定向到STDOUT_FILENO,这样plugin处理过程中,无需记录下输入/输出fd;
  2. 父进程一侧,从stdin_pipe[1]写入数据,然后从stdout_pipe[0]读数据;

父子进程通信接口定义说明:

定义格式在compiler/plugin.proto中,也是采用proto方式来完成自定义的。

CodeGeneratorRequest按照文件粒度FileDescriptorProto来提供:

1
2
3
4
5
		message CodeGeneratorRequest {
repeated string file_to_generate = 1;
optional string parameter = 2;
repeated FileDescriptorProto proto_file = 15;
}

CodeGeneratorResponse 返回结果中,返回文件内容是直接用string表达,protoc主进程直接负责后续持久化:

1
2
3
4
5
6
7
8
9
10
11
message CodeGeneratorResponse {
optional string error = 1;

// Represents a single generated file.
message File {
optional string name = 1;
optional string insertion_point = 2;
optional string content = 15;
}
repeated File file = 15;
}

plugin实现方式:

自定义 CodeGenerator类的子类MyCodeGenerator,在plugin进程的main函数中直接调用google::protobuf::compiler::PluginMain()

1
2
3
4
int main(int argc, char* argv[]) {
MyCodeGenerator generator;
return google::protobuf::compiler::PluginMain(argc, argv, &generator);
}

google::protobuf::compiler::PluginMain()的功能:

  1. 从STDIN_FILENO读取protoc主进程的输入数据,并且反序列化到CodeGeneratorRequest request;
  2. 从FileDescriptorProto产出FileDescriptor
  3. 调用MyCodeGenerator::Generate()
  4. 输出CodeGeneratorResponse response序列化之后的结果到STDOUT_FILENO,提供给protoc主进程

Subprocess类

  1. 负责建立父子进程之间的pipe,启动子进程
  2. 完成父子进程之间通信的数据格式转换