-
Notifications
You must be signed in to change notification settings - Fork 718
Examples
A set of easy examples, in lieu of documentation
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.
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.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 optionalb
field -- required fields do not have ahas_
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
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 fieldXXX
, in this casen_c
for the fieldc
.
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.
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.
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;
}
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;
}
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
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 Submessage
s. 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
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:
- they need a copy of
protoc-c
that runs on theirbuild
environment (thebuild
environment is for the machine on which they develop the application. - 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).