protobuf官方版没有C语言实现, 只有C++的实现. 本文给出一种C语言的极简
实现方案.
至于protobuf的编码协议, 读者自己去官方看吧, 不复杂. 一定要看懂, 否则怎么造轮子
以下是已经制作好的工具, 将proto文件转为c文件的代码生成器(Java实现).
https://github.com/wzjwhut/protobuf-to-c/tree/master/protobuf_c_generator/bin
使用方法:
- 将proto文件,
protoc.exe
,protoc.jar
放在同一个文件夹中 - 执行
java -jar protoc.jar your.proto
项目中已经有测试用的demo
将protobuf_c.c
, protobuf_c.h
加入工程一起编译
c接口使用例子
uint8_t* data;
int out_len;
{
/** 序列化的例子 */
MyType mytype;
MyProto myproto;
MyType_init(&mytype);
MyType_set_i32(&mytype, 123);
MyProto_init(&myproto);
MyProto_set_i32(&myproto, 456);
MyProto_set_i64(&myproto, 789);
pb_string str = PB_STR("hello");
MyProto_set_str(&myproto, &str);
MyProto_set_msg(&myproto, &mytype);
data = MyProto_to_byte_array(&myproto, &out_len);
log_hex("", data, out_len);
//输出 0a 05 68 65 6c 6c 6f 10 c8 03 18 95 06 22 02 08 7b
}
{
/** 反序列化的例子 */
MyProto myproto;
MyProto_init(&myproto);
MyProto_from_byte_array(&myproto, data, out_len);
printf("%.*s", myproto.str.size, myproto.str.data);
}
以下是造轮子的过程.
基本思路是:
3. 先做个简单的c版的demo
4. 再根据demo, 使用protobuf的java工具, 造一个代码生成器
分析规律
准备工作
先准备一些参考代码(不可能从头写)
下载protobuf源码和代码生成器, 编写一个proto文件. 如下(以下的过程都是以这个文件为例子)
MyProto.proto
syntax = "proto2";
message MyType {
optional int32 i32 = 1;
}
message MyProto{
enum MessageType { TYPE1 = 1; TYPE2 = 2;}
optional MessageType message_type = 12;
optional string str = 1 [default = "hello world"];
optional int32 i32 = 2;
optional MyType msg = 4;
}
然后执行
protoc.exe --cpp_out=./ MyProto.proto
会生成c++源文件和头文件, 这两个文件仅仅是用来做例子的. 造轮子也得要有个参考嘛
需要重点看的函数有
set_xxx
, has_xxx
, 看一两个就够了, 都是一样的模式
ByteSizeLong
用于计算编码后的字节数
MergePartialFromCodedStream
将字节流反序列化为对象
SerializeWithCachedSizes
将对象序列化为字节流.
其它的C++/C++11相关的东西跳过, 不要了.
打开protobuf源码目录protobuf-master/src/google/protobuf
需要重点看的文件有
wire_format.cc
wire_format_lite.cc
message.cc
message_lite.cc
这些是protobuf的各种类型的编码函数, 需要从这里面抠代码出来
分析和改造C++版的代码
将c++的代码改造成c代码,
删除C++/C++11的移动构造函数
, 拷贝构造函数
, 拷贝赋值函数
, 移动赋值函数
等
使用struct
代替class
使用指针
代替引用
成员函数
改成C语言风格,
构造函数
改为普通的函数
比如XXX::set_xx(int v)
改成XXX_set_xx(XXX* , int)
使用使用自定义的结构体代替string
HasBits
直接使用uint32[]
数组代替, 这个变量用来记录哪些成员变量赋过值
bool
使用自定义的类型代替typedef int PBOOL;
使用inline static
代替inline
使用结构体嵌套
代替继承
最终让编译器通过, 改造之后的样子类似于
struct MyProto{
pb_message _imessage;
uint32 _has_bits_[2];
size_t _cached_size_;
MyType msg_;
int32 i32_;
int message_type_;
pb_string str_;
PBOOL _inited;
};
inline static PBOOL MyType_has_i32(MyType* proto) {
return (proto->_has_bits_[0] & 0x00000001u) != 0;
}
inline static void MyType_set_has_i32(MyType* proto) {
proto->_has_bits_[0] |= 0x00000001u;
}
PBOOL MyProto_MergePartialFromCodedStream(MyProto* proto, pb_inputstream* input) {
#define DO_(EXPRESSION) if (!(EXPRESSION)) goto failure
uint32 tag;
for (;;) {
if (ReadTag(input, &tag)) goto handle_unusual;
switch (GetTagFieldNumber(tag)) {
case 1: {
if (tag == 10u ) {
MyProto_set_has_str(proto);
DO_(ReadString(input, &proto->str_));
} else {
goto handle_unusual;
}
break;
}
// optional int32 i32 = 2;
case 2: {
if (tag ==16u) {
MyProto_set_has_i32(proto);
DO_(ReadInt32(input, &proto->i32_));
} else {
goto handle_unusual;
}
break;
}
// optional .MyType msg = 4;
case 4: {
if (tag == 34u) {
MyProto_set_has_msg(proto);
if(!proto->msg_._inited){
MyType_init(&proto->msg_);
}
DO_(ReadMessage(input, (pb_message*)&proto->msg_));
} else {
goto handle_unusual;
}
break;
}
// optional .MyProto.MessageType message_type = 12;
case 12: {
if (tag ==96u) {
DO_(ReadEnum(input, (int32_t*)&proto->message_type_));
} else {
goto handle_unusual;
}
break;
}
default: {
handle_unusual:
if (tag == 0) {
goto success;
}
DO_(SkipField(input, tag));
break;
}
}
}
success:
return true;
failure:
return false;
#undef DO_
}
总结出规律之后, 使用protobuf的java版解析器处理proto文件, 自动生成C文件
com.google.protobuf.DescriptorProtos