C Guide

This document will introduce how to use bitproto with C language.

Compile bitproto for C

Firstly, run the bitproto compiler to generate code for C:

$ bitproto c pen.bitproto

Where the pen.bitproto is introduced in earlier section An example bitproto.

We will find that bitproto generates us two files in current directory:

  • pen_bp.h: Contains the declarations of structs, macros and api functions etc.

  • pen_bp.c: Contains the function implementations.

It’s recommended to open this two generated files to have a look. In the generated file pen_bp.h:

  • The enum Color in bitproto is mapped to a typedef statement in C, and the enum values are mapped to macros:

    typedef uint8_t Color; // 3bit
    
    #define COLOR_UNKNOWN 0
    #define COLOR_RED 1
    #define COLOR_BLUE 2
    #define COLOR_GREEN 3
    
  • The Timestamp type in bitproto is mapped to a typedef in C:

    typedef int64_t Timestamp; // 64bit
    
  • The message Pen in bitproto is mapped to a struct in C:

    struct Pen {
        Color color; // 3bit
        Timestamp produced_at; // 64bit
    };
    
  • The compiler also generates three functions, they are the encoder, decoder and json formatter.

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

Download bitproto C library

Bitproto serialization requires a language-specific library to work, the generated encoder and decoder depends on the bitproto C library underlying.

Download the bitproto library for C language from this github link, and put them (the bitproto.c and bitproto.h) to current working directory.

Run the code

Now, we create a file named main.c and put the following code in it:

#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;
}

In the code above, we firstly create a p of type struct Pen with data initilization, then call a function EncodePen to encode p into buffer s. The length of buffer s is generated by compiler as a macro defined as BYTES_LENGTH_PEN.

In the decoding part, we construct another p1 instance of type struct Pen with zero initilization, then call a function DecodePen to decode bytes from buffer s into p1.

Finally, use a function JsonPen generated by the compiler to format the structure p1 to json string to checkout if the decoding works ok.

Let’s compile it with the C library bitproto.c and generated pen_bp.c, and run:

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

The encoder and decoder copy bits between the structure’s memory and buffer s byte-to-byte, the whole call allocates memory on the stack without any dynamic allocations.

There’s another larger example source code on the github.

Naming Prefix

As we know, there’s no namespace mechanism to scope definition names across including header files in C. Bitproto provides an option to add a name prefix to all generated types. To use it, define an option at the global scope of the bitproto file:

option c.name_prefix = "my_prefix_"

Run the bitproto compiler again, we will that names in pen_bp.h are changed:

  • The enum Color is now mapped to MyPrefixColor.

  • The Timestamp is now mapped to MyPrefixTimestamp.

  • The message Pen is now mapped to struct MyPrefixPen.