Nanopb has a heavier interface for including sub-messages. Here's
an example, from their union structure:
// This is an example of how to handle 'union' style messages
// with nanopb, without allocating memory for all the message types.
//
// There is no official type in Protocol Buffers for describing unions,
// but they are commonly implemented by filling out exactly one of
// several optional fields.
message MsgType1 {
required int32 value = 1;
}
message MsgType2 {
required bool value = 1;
}
message MsgType3 {
required int32 value1 = 1;
required int32 value2 = 2;
}
message UnionMessage {
optional MsgType1 msg1 = 1;
optional MsgType2 msg2 = 2;
optional MsgType3 msg3 = 3;
}
it's encoded with:
/* This function is the core of the union encoding process. It handles
* the top-level pb_field_t array manually, in order to encode a correct
* field tag before the message. The pointer to MsgType_fields array is
* used as an unique identifier for the message type.
*/
bool encode_unionmessage(pb_ostream_t *stream, const pb_field_t messagetype[], const void *message)
{
const pb_field_t *field;
for (field = UnionMessage_fields; field->tag != 0; field++)
{
if (field->ptr == messagetype)
{
/* This is our field, encode the message using it. */
if (!pb_encode_tag_for_field(stream, field))
return false;
return pb_encode_submessage(stream, messagetype, message);
}
}
/* Didn't find the field for messagetype */
return false;
}
Their low-level code is necessary because "Usually, nanopb would
allocate space to store
all of the possible messages at the same time, even though at most
one of
them will be used at a time.
"
My library would encode such a thing by calling the top-level
(auto-generated) function:
buf = UnionMessage_to_string(&len, (MY_UnionMessage
*)&test);
and providing copy functions for each message. The auto-generated
example code to use the interface is below:
void protowr_MsgType1(MsgType1 *out, MY_MsgType1 *a) {
out->value = a->value;
}
void protowr_MsgType2(MsgType2 *out, MY_MsgType2 *a) {
out->value = a->value;
}
void protowr_MsgType3(MsgType3 *out, MY_MsgType3 *a) {
out->value1 = a->value1;
out->value2 = a->value2;
}
// of course, the programmer would modify this to a case()
statement.
void protowr_UnionMessage(UnionMessage *out, MY_UnionMessage *a) {
out->has_msg1 = a->has_msg1;
if(a->has_msg1) {
out->msg1 = a->msg1;
}
out->has_msg2 = a->has_msg2;
if(a->has_msg2) {
out->msg2 = a->msg2;
}
out->has_msg3 = a->has_msg3;
if(a->has_msg3) {
out->msg3 = a->msg3;
}
}
There is no dealing with the byte-level encoding at this level, only
copying between a shallow parsed format and your own output data
structure, one message at a time. It also doesn't litter
message-specific field tags or names anywhere - only high-level,
per-message encoding/decoding functions. I don't know if nanopb
does this, but some implementations do.
Hope that helps,
~ David.