C 语言引导

本文将介绍如何在 C 语言中使用 bitproto 。

编译 bitproto 生成 C 文件

首先,我们执行 bitproto 的编译器来生成 C 语言的代码:

$ bitproto c pen.bitproto

其中, pen.bitproto 已经在前面的章节 一个示例 bitproto 中做过说明。

我们会发现 bitproto 会为我们生成两个文件:

  • pen_bp.h : 这个头文件包含了结构体、宏和接口函数的声明。

  • pen_bp.c : 这个文件包含了函数的实现。

我们推荐你使用编辑器打开生成的两个文件,查看一下生成的文件的样子。在生成的 pen_bp.h 文件中:

  • bitproto 中的 enum Color 被映射成了一个 C 语言中的 typedef 语句,枚举的值被映射成了宏语句:

    typedef uint8_t Color; // 3bit
    
    #define COLOR_UNKNOWN 0
    #define COLOR_RED 1
    #define COLOR_BLUE 2
    #define COLOR_GREEN 3
    
  • bitproto 中的 Timestamp 类型被映射成了一个 C 语言中的 typedef 语句:

    typedef int64_t Timestamp; // 64bit
    
  • bitproto 中的消息 Pen 被映射成了一个 C 语言中的 struct 结构体:

    struct Pen {
        Color color; // 3bit
        Timestamp produced_at; // 64bit
    };
    
  • bitproto 的编译器同样会生成 3 个函数,分别是 编码函数、解码函数 和 JSON 格式化函数:

    int EncodePen(struct Pen *m, unsigned char *s);
    
    int DecodePen(struct Pen *m, unsigned char *s);
    
    int JsonPen(struct Pen *m, char *s);
    

下载 bitproto 的 C 语言支持库

bitproto 的序列化需要依赖目标语言的支持库来工作,在这里,我们生成的编解码函数依赖 bitproto 的 C 语言支持库。

可以从 Github 的这个链接 来下载 bitproto 对 C 语言的支持库。下载后,把文件 bitproto.cbitproto.h 放在当前目录下。

运行代码

现在,我们创建一个叫做 main.c 的文件,包含以下代码

#include "pen_bp.h"
#include <stdio.h>  // for `printf`

int main() {
  struct Pen p = {COLOR_RED, 1611515729966};
  unsigned char s[BYTES_LENGTH_PEN] = {0};

  // Encode p to buffer s.
  EncodePen(&p, s);

  // Decode buffer s to p1.
  struct Pen p1 = {};
  DecodePen(&p1, s);

  // Format p1 to buffer buf.
  char buf[64] = {0};
  JsonPen(&p1, buf);
  printf("%s", buf);

  return 0;
}

上面的代码中,首先创建了一个 struct Pen 类型的实例 p ,并做了数据上的初始化。然后执行了一个函数 EncodePen 来编码 p 到字节流 s 。其中,字节流 s 的长度是编译器生成的宏 BYTES_LENGTH_PEN

在解码的部分,首先创建了 struct Pen 的另一个实例 p1 ,然后执行函数 DecodePen 来从字节流 s 中解码数据到 p1 中。

最后,使用了函数 JsonPen 来把 p1 格式化成为一个 JSON 字符串,打印来检验我们的编解码是否正常工作。

现在我们来带入 bitproto.c 和生成的 pen_bp.c 一起和 main.c 进行编译,然后执行 :

$ cc main.c bitproto.c pen_bp.c -o main
$ ./main
{"color":1,"produced_at":1611515729966}

编码和解码函数会一个字节一个字节的在结构体和字节流之间拷贝比特数据,整个调用都是在栈上申请内存,没有任何动态内存分配。

github 上有一个更大一些的例子。

命名前缀

我们都知道,在 C 语言中没有一种命名空间的机制来。bitproto 提供了一个支持命名前缀的选项,这样可以添加一个命名前缀到所有生成类型的名字上。用法是这样的,在 bitproto 的文件中,全局维度定义一个选项:

option c.name_prefix = "my_prefix_"

再次执行 bitproto 的编译器,我们可以看到 pen_bp.h 中的名字全部变了:

  • enum Color 现在映射成了 MyPrefixColor

  • Timestamp 现在映射成了 MyPrefixTimestamp

  • message Pen 现在映射成了 struct MyPrefixPen