Skip to content
John Calcote edited this page Mar 8, 2024 · 32 revisions

A set of easy examples, in lieu of documentation

Introduction

Examples of using protobuf-c.

protobuf-c works by taking a .proto file (which is defined by google's Protocol Buffers library), and generating both .h and .c files for use in C programs.

guy shoveling Warning: this page is partial, incomplete and the code is untested. Eventually I plan to make an "examples" directory in the .tar.gz so you don't need to cut-n-paste.

Simple complete example

A simple file, amessage.proto file following the language guide:

message AMessage {
  required int32 a=1; 
  optional int32 b=2;
}

Generate .h and .c files from the command-line in your current working directory:

 protoc-c --c_out=. amessage.proto 

Serialize/pack the AMessage as follows:

#include <stdio.h>
#include <stdlib.h>
#include "amessage.pb-c.h"

int main (int argc, const char * argv[]) 
{
  AMessage msg = AMESSAGE__INIT; // AMessage
  void *buf;                     // Buffer to store serialized data
  unsigned len;                  // Length of serialized data
  
  if (argc != 2 && argc != 3)
  {   // Allow one or two integers
    fprintf(stderr,"usage: amessage a [b]\n");
    return 1;
  }
  
  msg.a = atoi(argv[1]);
  if (argc == 3) { msg.has_b = 1; msg.b = atoi(argv[2]); }
  len = amessage__get_packed_size(&msg);
  
  buf = malloc(len);
  amessage__pack(&msg,buf);
  
  fprintf(stderr,"Writing %d serialized bytes\n",len); // See the length of message
  fwrite(buf,len,1,stdout); // Write to stdout to allow direct command line piping
  
  free(buf); // Free the allocated serialized buffer
  return 0;
}

I left most error handling out for brevity. Notice:

  • the use of the AMESSAGE__INIT macro to construct the message
  • the has_b member corresponds to the optional b field -- required fields do not have a has_ member.
  • amessage__get_packed_size returns the length of the packed data.
  • amessage__pack serializes the message.

On the other hand, to deserialize/unpack a message, try code like this:

#include <stdio.h>
#include <stdlib.h>
#include "amessage.pb-c.h"
#define MAX_MSG_SIZE 1024

static size_t
read_buffer (unsigned max_length, uint8_t *out)
{
  size_t cur_len = 0;
  size_t nread;
  while ((nread=fread(out + cur_len, 1, max_length - cur_len, stdin)) != 0)
  {
    cur_len += nread;
    if (cur_len == max_length)
    {
      fprintf(stderr, "max message length exceeded\n");
      exit(1);
    }
  }
  return cur_len;
}


int main (int argc, const char * argv[]) 
{
  AMessage *msg;

  // Read packed message from standard-input.
  uint8_t buf[MAX_MSG_SIZE];
  size_t msg_len = read_buffer (MAX_MSG_SIZE, buf);

  // Unpack the message using protobuf-c.
  msg = amessage__unpack(NULL, msg_len, buf);	
  if (msg == NULL)
  {
    fprintf(stderr, "error unpacking incoming message\n");
    exit(1);
  }

  // display the message's fields.
  printf("Received: a=%d",msg->a);  // required field
  if (msg->has_b)                   // handle optional field
    printf("  b=%d",msg->b);
  printf("\n");

  // Free the unpacked message
  amessage__free_unpacked(msg, NULL);
  return 0;
}

During linking each above program, make sure to include '-lprotobuf-c'

Test by piping one program into the next at command line:

./amessage_serialize 10 2 | ./amessage_deserialize 
Writing: 4 serialized bytes
Received: a=10 b=2

Repeated Fields

Here is a simple file, cmessage.proto file:

message CMessage {
  repeated int32 c=1;
}

Serialize/pack the CMessage as follows:

#include <stdio.h>
#include <stdlib.h>
#include "cmessage.pb-c.h"

int main (int argc, const char * argv[]) 
{
  CMessage msg = CMESSAGE__INIT;  // CMessage (repeated int32)
  void *buf;                      // Buffer to store serialized data
  unsigned len,i;                 // Length of serialized data
  
  msg.n_c = argc - 1;                      // Save number of repeated int32
  msg.c = malloc (sizeof (int) * msg.n_c); // Allocate memory to store int32
  for (i = 0; i < msg.n_c; i++)
      msg.c[i] = atoi (argv[i+1]);         // Access msg.c[] as array 
  len = cmessage__get_packed_size (&msg);  // This is calculated packing length
  buf = malloc (len);                      // Allocate required serialized buffer length 
  cmessage__pack (&msg, buf);              // Pack the data

  fprintf(stderr,"Writing %d serialized bytes\n",len); // See the length of message
  fwrite (buf, len, 1, stdout);            // Write to stdout to allow direct command line piping

  free (msg.c); // Free storage for repeated int32             
  free (buf);   // Free serialized buffer
  return 0;
}

I left most error handling out for brevity. Notice:

  • the use of the CMESSAGE__INIT macro to construct the message
  • the n_XXX member is generated for a repeated field XXX, in this case n_c for the field c.

On the other hand, if you want to deserialize/unpack a message, try code like this:

#include <stdio.h>
#include "cmessage.pb-c.h"
#define MAX_MSG_SIZE  4096

int main (int argc, const char * argv[]) 
{
  CMessage *msg;
  uint8_t buf[MAX_MSG_SIZE];
  unsigned i;
  size_t msg_len = read_buffer (MAX_MSG_SIZE, buf);

  msg = cmessage__unpack (NULL, msg_len, buf); // Deserialize the serialized input
  if (msg == NULL)
  { // Something failed
    fprintf(stderr, "error unpacking incoming message\n");
    return 1;
  }
  
  for (i = 0; i < msg->n_c; i++)
  { // Iterate through all repeated int32
    if (i > 0)
      printf (", ");
    printf ("%d", msg->c[i]);
  }
  printf ("\n");
  
  cmessage__free_unpacked(msg,NULL); // Free the message from unpack()
  return 0;
}

Test by piping one program into the next at command line:

./cmessage_serialize 12 3 4 | ./cmessage_deserialize 
Writing: 6 serialized bytes
12, 3, 4

Now here is a more complex example:

message Inner {
    required int32 a = 1;
    required int32 b = 2;
}

message CMessage {
    repeated Inner c = 1;
}

This works similarly to the above example, except that msg.c is going to expect an array of pointers. This means that msg.c[i] is expecting a pointer to a previously allocated Inner structure, not a Inner structure.

Strings

Here is a simple file, dmessage.proto file:

message DMessage {
  repeated string d=1;
}

Serialize/pack the DMessage as follows:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "dmessage.pb-c.h"

int main (int argc, const char * argv[])
{
  DMessage msg = DMESSAGE__INIT;  // DMessage (repeated string)
  void *buf;                      // Buffer to store serialized data
  unsigned len,i,j;               // Length of serialized data

  msg.n_d = argc - 1;                      // Save number of repeated strings
  msg.d = malloc (sizeof (char*) * msg.n_d);   // Allocate memory to store strings
  for (j = 0; j < msg.n_d; j++) {
      msg.d[j] = (char*)argv[j+1];         // Access msg.c[] as array
  }
  len = dmessage__get_packed_size (&msg);  // This is calculated packing length
  buf = malloc (len);                      // Allocate required serialized buffer length
  dmessage__pack (&msg, buf);              // Pack the data

  fprintf(stderr,"Writing %d serialized bytes\n",len); // See the length of message
  fwrite (buf, len, 1, stdout);            // Write to stdout to allow direct command line piping

  free (msg.d); // Free storage for repeated string
  free (buf);   // Free serialized buffer
  return 0;
}

I left most error handling and avoided realloc out for brevity. Notice:

  • the use of the DMESSAGE__INIT macro to construct the message
  • the n_XXX member is generated for a repeated field XXX.

On the other hand, if you want to deserialize/unpack a message, try code like this:

#include <stdio.h>
#include "dmessage.pb-c.h"
#define MAX_MSG_SIZE  4096

int main (int argc, const char * argv[])
{
  DMessage *msg;
  uint8_t buf[MAX_MSG_SIZE];
  unsigned i;
  size_t msg_len = read_buffer(MAX_MSG_SIZE, buf);

  msg = dmessage__unpack (NULL, msg_len, buf); // Deserialize the serialized input
  if (msg == NULL)
  { // Something failed
      fprintf(stderr, "error unpacking incoming message\n");
      return 1;
  }

  for (i = 0; i < msg->n_d; i++)
  { // Iterate through all repeated strings
      if (i > 0)
          printf (", ");
      printf("%s", msg->d[i]);
  }
  printf ("\n");

  dmessage__free_unpacked(msg,NULL); // Free the message from unpack()
  return 0;
}

Test by piping one program into the next at command line:

./dmessage_serialize hello beautiful world | ./dmessage_deserialize
Writing 25 serialized bytes
hello, beautiful, world

Note that 'has' fields are not generated for optional strings. To test for a string, check the field for NULL.

Bytes

Here is a simple file, dmessage.proto file:

syntax = "proto3";

message DMessage {
  bytes name=1;
  bytes value=2;
}

Serialize/pack the DMessage as follows:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "dmessage.pb-c.h"

#ifndef ARRAY_SIZE
#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr)[0])
#endif

int main(int argc, const char * argv[])
{
    size_t i = 0;
    uint32_t len = 0;
    DMessage msg;

    uint8_t name[] = {0x78, 0x28, 0x33, 0x75, 0x22, 0x90, 0x11};
    uint8_t value[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF};

    dmessage__init(&msg);

    msg.name.len = sizeof(name);
    msg.name.data = name;

    msg.value.len = sizeof(value);
    msg.value.data = value;

    //pack message
    len = dmessage__get_packed_size(&msg);
    printf("packed size:%u Byte\n\n", len);
    uint8_t *buffer = (uint8_t *)calloc(len, sizeof(char));
    dmessage__pack(&msg, buffer);

    //--------------------------------------------------------------------------//

    //unpack message
    DMessage *msg1 = dmessage__unpack(NULL, len, buffer);

    printf("name:\n");
    for(i = 0; i < msg1->name.len; i++)
    {
        printf("%02X", msg1->name.data[i] & 0xFF);
    }
    printf("\n");

    printf("value:\n");
    for(i = 0; i < msg1->value.len; i++)
    {
        printf("%02X", msg1->value.data[i] & 0xFF);
    }
    printf("\n");

    free(buffer);
    dmessage__free_unpacked(msg1, NULL);
    return 0;
}

Oneofs

Here is a simple file, dmessage.proto file demonstrate oneof usage:

syntax = "proto3";

message DMessage {
  repeated string d=1;
  oneof oneofName {
    string name = 2;
  }
  oneof oneofValue{
    string value = 3;
  }
  oneof oneofInteger{
    int32 a = 4;
    int32 b = 5;
  }
}

Serialize/pack the DMessage as follows:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "dmessage.pb-c.h"

#ifndef ARRAY_SIZE
#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr)[0])
#endif

int main(int argc, const char * argv[])
{
    size_t i = 0;
    uint32_t len = 0;
    DMessage msg;

    char *array[] =
    {
        "foobar2000 is an advanced freeware audio player for the Windows platform.",
        "foobar2000 v1.6.7 final has been released."
    };

    dmessage__init(&msg);

    msg.n_d = ARRAY_SIZE(array);
    msg.d = (char **)calloc(msg.n_d * sizeof(char *), sizeof(char));
    for(i = 0; i < ARRAY_SIZE(array); i++)
    {
        msg.d[i] = array[i];
    }

    // sender not set name field
    msg.oneof_name_case = DMESSAGE__ONEOF_NAME__NOT_SET;
    msg.name = "hello";

    // sender set value field
    msg.oneof_value_case = DMESSAGE__ONEOF_VALUE_VALUE;
    msg.value = "world";

    // sender set int b field
    msg.oneof_integer_case = DMESSAGE__ONEOF_INTEGER_B;
    msg.b = 7805;

    //pack message
    len = dmessage__get_packed_size(&msg);
    printf("packed size:%u Byte\n\n", len);
    uint8_t *buffer = (uint8_t *)calloc(len, sizeof(char));
    dmessage__pack(&msg, buffer);
    free(msg.d);

    //--------------------------------------------------------------------------//

    //unpack message
    DMessage *msg1 = dmessage__unpack(NULL, len, buffer);

    for(i = 0; i < msg1->n_d; i++)
    {
        printf("msg1[%zu]:%s\n", i, msg1->d[i]);
    }

    // check name field presence or not
    if(msg1->oneof_name_case == DMESSAGE__ONEOF_NAME_NAME)
    {
        printf("name:%s\n", msg1->name);
    }

    // check value field presence or not
    if(msg1->oneof_value_case == DMESSAGE__ONEOF_VALUE_VALUE)
    {
        printf("value:%s\n", msg1->value);
    }

    // check a or b field presence
    if(msg1->oneof_integer_case == DMESSAGE__ONEOF_INTEGER_A)
    {
        printf("int a:%d\n", msg1->a);
    }
    else if(msg1->oneof_integer_case == DMESSAGE__ONEOF_INTEGER_B)
    {
        printf("int b:%d\n", msg1->b);
    }

    free(buffer);
    dmessage__free_unpacked(msg1, NULL);
    return 0;
}

Constructing Submessages

Here is a simple file, emessage.proto file:

message Submessage {
  required int32 value=1;
}

message EMessage {
  required Submessage a=1;
  optional Submessage b=2;
}

Here EMessage consists of one or two integers (a is one int and required; b is one int and optional).

#include <stdio.h>
#include <stdlib.h>
#include "emessage.pb-c.h"

int main (int argc, const char * argv[]) 
{
  EMessage msg    = EMESSAGE__INIT;   // EMESSAGE
  Submessage sub1 = SUBMESSAGE__INIT; // SUBMESSAGE A
  Submessage sub2 = SUBMESSAGE__INIT; // SUBMESSAGE B
  void *buf;
  unsigned len;
  
  if (argc != 2 && argc != 3)
  { // Allow one or two integers
    fprintf(stderr,"usage: emessage a [b]\n");
    return 1;
  }
  sub1.value = atoi (argv[1]); 
  msg.a = &sub1;               // Point msg.a to sub1 data
  
  // NOTE: has_b is not required like amessage, therefore check for NULL on deserialze
  if (argc == 3) { sub2.value = atoi (argv[2]); msg.b = &sub2; } // Point msg.b to sub2 data
  
  len = emessage__get_packed_size (&msg); // This is the calculated packing length
  buf = malloc (len);                     // Allocate memory
  emessage__pack (&msg, buf);             // Pack msg, including submessages

  fprintf(stderr,"Writing %d serialized bytes\n",len); // See the length of message
  fwrite (buf, len, 1, stdout);           // Write to stdout to allow direct command line piping
  
  free(buf); // Free the allocated serialized buffer
  return 0;
}

Notice:

  • there is no has_ flag for optional submessages -- if the pointer is non-NULL, then we assume that it is a value.

On the other hand, if you want to deserialize/unpack a message, try code like this:

#include <stdio.h>
#include "emessage.pb-c.h"
#define MAX_MSG_SIZE 4096

int main (int argc, const char * argv[]) 
{
  EMessage *msg;         // EMessage using submessages
  Submessage *sub1,*sub2;// Submessages
  char c; int i=0;       // Data holders
  uint8_t buf[MAX_MSG_SIZE]; // Input data container for bytes
    
  while (fread(&c,1,1,stdin) != 0)
  {
    if (i >= MAX_MSG_SIZE)
    {
      fprintf(stderr,"message too long for allocated buffer\n");
      return 1;
    }
    buf[i++] = c;
  }
    
  msg = emessage__unpack(NULL,i,buf); // Deserialize the serialized input
  if (msg == NULL)
  { // Something failed
    fprintf(stderr,"error unpacking incoming message\n");
    return 1;
  }
  sub1 = msg->a; sub2 = msg->b;
  printf("Received: a=%d",sub1->value);
  if (msg->b != NULL) printf(" b=%d",sub2->value);
  printf("\n");
    
  emessage__free_unpacked(msg,NULL);
    
  return 0;
}

Test by piping one program into the next at command line:

./emessage_serialize 4 5 | ./emessage_deserialize 
Writing: 8 serialized bytes
Received: a=4 b=5
./emessage_serialize 4 | ./emessage_deserialize 
Writing: 4 serialized bytes
Received: a=4

Constructing Repeated Submessages

Here is a simple file, fmessage.proto file:

message Submessage {
  required int32 value=1;
}

message FMessage {
  repeated Submessage a=1;
}

Here FMessage consists of 0 to many Submessages. Each Submessage contains exactly one int.

#include <stdio.h>
#include <stdlib.h>
#include "fmessage.pb-c.h"

int main (int argc, const char * argv[]) 
{
  FMessage msg = FMESSAGE__INIT;
  Submessage **subs;
  void *buf;
  unsigned len,i;
  subs = malloc (sizeof (Submessage*) * (argc-1));
  for (i = 1; i < argc; i++)
  {
    subs[i-1] = malloc (sizeof (Submessage));
    submessage__init (subs[i-1]);
    subs[i-1]->value = atoi(argv[i]);
  }    
  msg.n_a = argc-1;
  msg.a = subs;
  len = fmessage__get_packed_size (&msg); // This is the calculated packing length
  buf = malloc (len);                     // Allocate memory
  fmessage__pack (&msg, buf);             // Pack msg, including submessages

  fprintf(stderr,"Writing %d serialized bytes\n",len); // See the length of message
  fwrite (buf, len, 1, stdout);           // Write to stdout to allow direct command line piping
    
  free(buf); // Free the allocated serialized buffer
  for (i = 1; i < argc; i++)
    free (subs[i]);
  free (subs); 
  return 0;
}

Notice that repeated fields create two fields, in this case n_a, the number of submessages, and a the submessages themselves. Also note that a is an array of pointers to messages.

On the other hand, if you want to deserialize/unpack a message, try code like this:

#include <stdio.h>
#include "fmessage.pb-c.h"
#define MAX_MSG_SIZE 4096

int main (int argc, const char * argv[]) 
{
    FMessage *msg;
    char c; int i=0;
    uint8_t buf[MAX_MSG_SIZE]; // Input data container for bytes
    
    while (fread(&c,1,1,stdin) != 0)
    {
        if (i >= MAX_MSG_SIZE)
        {
            fprintf(stderr,"message too long for allocated buffer\n");
            return 1;
        }
        buf[i++] = c;
    }
    
    msg = fmessage__unpack(NULL,i,buf); // Deserialize the serialized input
    if (msg == NULL)
    {
        fprintf(stderr,"error unpacking incoming message\n");
        return 1;
    }
    for (i = 0; i < msg->n_a; i++)
       printf ("%d\n", msg->a[i]->value);
    
    fmessage__free_unpacked(msg,NULL);
    
    return 0;
}

Test by piping one program into the next at command line:

./fmessage_serialize 4 5 | ./fmessage_deserialize 
Writing: 8 serialized bytes
4
5
./fmessage_serialize 4 5 8| ./fmessage_deserialize 
4
5
8

Using the Allocator

Packing with an Append Function

Language Binding Hints

Protobuf-C For Embedded Developers (and other cross compilers)

TODO: move these general comments to a new page and provide an example, as promised by the page title.

Most embedded developers need to compile protobuf-c twice:

  1. they need a copy of protoc-c that runs on their build environment (the build environment is for the machine on which they develop the application.
  2. they need to have a copy of libprotobuf-c, which is the runtime library, that is compiled for the embedded platform (which we will call the target environment).

For (1), you need to install the main protobuf package, since protoc-c uses its parser and code generator libraries. Running ./configure as usual should be fine.

For (2), you can configure with --disable-protoc which means you won't have to build the main protobuf package for the embedded platform (since you don't need to do code gen on the embedded platform). The usual details of cross-compilation for your specific embedded platform apply. Sometimes merely providing the name of the platform to configure suffices (as in, ./configure atmel) but other environments need a mass of overrides like CC=atmel-cc LD=atmel-ld.

If embedded developers do not modify the .proto files, then then all they really need are the generated .pb-c.[ch] files, and then they can skip (1).