protobuf纯C语言实现

protobuf官方版没有C语言实现, 只有C++的实现. 本文给出一种C语言的极简实现方案.
至于protobuf的编码协议, 读者自己去官方看吧, 不复杂. 一定要看懂, 否则怎么造轮子

以下是已经制作好的工具, 将proto文件转为c文件的代码生成器(Java实现).
https://github.com/wzjwhut/protobuf-to-c/tree/master/protobuf_c_generator/bin
使用方法:

  1. 将proto文件, protoc.exe, protoc.jar放在同一个文件夹中
  2. 执行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

猜你喜欢

转载自blog.csdn.net/wzj_whut/article/details/86666820