如何使用nanopb将自定义结构体编码为Protobuf格式?
使用nanopb编码自定义结构体为Protobuf:可行方案与回调实现
完全可行,nanopb的回调编码机制专门用来处理自定义数据结构与Protobuf消息的映射场景,无需将自定义结构体转换为nanopb生成的结构体,直接通过回调函数完成编码。以下是基于你提供的定义的完整实现:
基础定义回顾
1. Proto文件定义
message DeviceInfo { string deviceId = 1; DataConnection connection = 2; } message DataConnection { repeated Field fields = 1; } message Field { string key = 1; string value = 2; }
2. nanopb生成的C结构体
typedef struct _DeviceInfo DeviceInfo; typedef struct _DataConnection DataConnection; typedef struct _Field Field; struct _Field { pb_callback_t key; pb_callback_t value; }; struct _DataConnection { pb_callback_t fields; }; struct _DeviceInfo { pb_callback_t deviceId; pb_callback_t connection; };
3. 自定义数据存储结构体及实例
// 自定义字段结构体 typedef struct { const char* key; const char* value; } CustomField; // 自定义连接结构体 typedef struct { CustomField* fields; size_t field_count; } CustomDataConnection; // 自定义设备信息结构体 typedef struct { const char* deviceId; CustomDataConnection connection; } CustomDeviceInfo; // 已填充的实例 CustomField sample_fields[] = { {"ip", "192.168.1.100"}, {"port", "8080"} }; CustomDataConnection sample_conn = { .fields = sample_fields, .field_count = sizeof(sample_fields)/sizeof(sample_fields[0]) }; CustomDeviceInfo sample_device = { .deviceId = "DEVICE_001", .connection = sample_conn };
4. 已实现的deviceId编码逻辑
bool encode_deviceId(pb_ostream_t* stream, const pb_field_t* field, void* const* arg) { const CustomDeviceInfo* device = (const CustomDeviceInfo*)*arg; if (!pb_encode_tag_for_field(stream, field)) { return false; } return pb_encode_string(stream, (const uint8_t*)device->deviceId, strlen(device->deviceId)); }
完整回调函数实现
1. 单个Field字段的编码回调
负责把CustomField转换为Protobuf的Field消息:
bool encode_field(pb_ostream_t* stream, const pb_field_t* field, void* const* arg) { const CustomField* custom_field = (const CustomField*)*arg; // 编码key字段 if (!pb_encode_tag(stream, PB_WIRETYPE_LENGTH_DELIMITED, Field_key_tag)) { return false; } if (!pb_encode_string(stream, (const uint8_t*)custom_field->key, strlen(custom_field->key))) { return false; } // 编码value字段 if (!pb_encode_tag(stream, PB_WIRETYPE_LENGTH_DELIMITED, Field_value_tag)) { return false; } if (!pb_encode_string(stream, (const uint8_t*)custom_field->value, strlen(custom_field->value))) { return false; } return true; }
2. DataConnection中repeated Fields的编码回调
循环遍历所有自定义字段,逐个调用encode_field完成重复字段编码:
bool encode_data_connection_fields(pb_ostream_t* stream, const pb_field_t* field, void* const* arg) { const CustomDataConnection* conn = (const CustomDataConnection*)*arg; for (size_t i = 0; i < conn->field_count; i++) { const CustomField* field_item = &conn->fields[i]; // 写入repeated字段的tag if (!pb_encode_tag_for_field(stream, field)) { return false; } // 预计算单个Field消息的长度 pb_ostream_t substream = PB_OSTREAM_SIZING; if (!encode_field(&substream, NULL, &field_item)) { return false; } // 写入消息长度和内容 if (!pb_encode_varint(stream, substream.bytes_written)) { return false; } if (!encode_field(stream, NULL, &field_item)) { return false; } } return true; }
3. DataConnection整体的编码回调
负责编码整个DataConnection消息,调用上述重复字段的回调:
bool encode_data_connection(pb_ostream_t* stream, const pb_field_t* field, void* const* arg) { const CustomDeviceInfo* device = (const CustomDeviceInfo*)*arg; // 写入DataConnection字段的tag if (!pb_encode_tag_for_field(stream, field)) { return false; } // 预计算DataConnection消息的总长度 pb_ostream_t substream = PB_OSTREAM_SIZING; if (!encode_data_connection_fields(&substream, DataConnection_fields_field, &device->connection)) { return false; } // 写入消息长度和内容 if (!pb_encode_varint(stream, substream.bytes_written)) { return false; } return encode_data_connection_fields(stream, DataConnection_fields_field, &device->connection); }
4. 整合执行完整编码
将回调关联到Protobuf结构体字段,执行编码流程:
int main() { uint8_t buffer[1024]; pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer)); DeviceInfo device_info = DeviceInfo_init_zero; // 绑定deviceId的回调 device_info.deviceId.funcs.encode = encode_deviceId; device_info.deviceId.arg = &sample_device; // 绑定connection的回调 device_info.connection.funcs.encode = encode_data_connection; device_info.connection.arg = &sample_device; // 执行编码 if (!pb_encode(&stream, DeviceInfo_fields, &device_info)) { printf("编码失败: %s\n", PB_GET_ERROR(&stream)); return 1; } printf("编码成功,字节数: %zu\n", stream.bytes_written); return 0; }
关键注意事项
- 直接使用nanopb生成的tag常量(如
Field_key_tag),避免手动写数值导致的错误 - 嵌套消息编码必须先预计算长度,再写入长度和消息内容,
PB_OSTREAM_SIZING是标准做法 - repeated字段需要为每个元素单独写入tag和长度
内容的提问来源于stack exchange,提问作者Markus Wimmer
相关产品推荐
相关产品推荐

