使用nanopb对protobuf中optional字符串字段进行编解码的问题
问题根源
nanopb默认不会为无长度限制的string/bytes类型分配内存,所以编译后对应字段会生成pb_callback_t类型,需要开发者自行实现编码/解码的回调逻辑处理字符串数据,这就是整型字段编解码正常、字符串字段无内容的核心原因。
两种解决方案
方案1:修改protobuf定义固定字符串最大长度(最简单,无需额外写回调)
如果业务场景下字符串长度可预估,直接在proto定义中添加nanopb扩展指定最大长度即可,重新编译后会直接生成固定大小的char数组,和整型字段一样直接读写即可,原有编解码逻辑无需修改。
修改后的proto示例:
import "nanopb.proto"; struct CDAPMessage { //... // 按业务实际需求调整max_size的数值,示例为最大128字节 optional string objName = 6 [(nanopb).max_size = 128]; optional int64 objInst = 7; //... }
重新编译生成.pb.h后,objName字段会变为char objName[128]+bool has_objName的结构,直接读写该字段即可完成编解码。
方案2:实现pb_callback_t回调逻辑(适合字符串长度不确定的场景)
如果确实需要支持任意长度的字符串,可自行实现编解码回调函数,步骤如下:
1. 定义存储字符串的上下文结构
// 用于存储解码后的字符串信息,可按自己的内存管理逻辑调整 typedef struct { char* str_buf; size_t str_len; } StringCtx;
2. 实现解码回调函数
bool string_decode_cb(pb_istream_t *stream, const pb_field_t *field, void **arg) { StringCtx *ctx = (StringCtx*)*arg; ctx->str_len = stream->bytes_left; // 分配内存,多1字节存储字符串结束符 ctx->str_buf = malloc(ctx->str_len + 1); if (!ctx->str_buf) return false; if (!pb_read(stream, (pb_byte_t*)ctx->str_buf, ctx->str_len)) { free(ctx->str_buf); ctx->str_buf = NULL; return false; } ctx->str_buf[ctx->str_len] = '\0'; return true; }
3. 修改解码函数绑定回调
注意:你原有代码存在栈变量返回的非法访问问题,CDAPMessage需要改为堆分配。
CDAPMessage *cdap_decode_msg(void *buf, size_t msg_len) { CDAPMessage *msg = malloc(sizeof(CDAPMessage)); if (!msg) return NULL; *msg = CDAPMessage_init_zero; // 为objName绑定回调和存储上下文,多个字符串字段需分别绑定独立的上下文 StringCtx obj_name_ctx = {0}; msg->objName.funcs.decode = string_decode_cb; msg->objName.arg = &obj_name_ctx; pb_istream_t stream = pb_istream_from_buffer(buf, msg_len); bool status = pb_decode(&stream, CDAPMessage_fields, msg); if (!status) { error("Decoding failed: %s\n", PB_GET_ERROR(&stream)); if (obj_name_ctx.str_buf) free(obj_name_ctx.str_buf); free(msg); return NULL; } // 解码完成后obj_name_ctx.str_buf即为objName的字符串内容,可按需存储到业务结构中 return msg; }
编码回调实现参考
编码逻辑和解码类似,实现对应回调函数将字符串内容写入流即可:
bool string_encode_cb(pb_ostream_t *stream, const pb_field_t *field, void * const *arg) { StringCtx *ctx = (StringCtx*)*arg; if (!pb_encode_tag_for_field(stream, field)) return false; return pb_encode_string(stream, (pb_byte_t*)ctx->str_buf, ctx->str_len); }
编码时将objName.funcs.encode指向该回调,arg指向存储待编码字符串的StringCtx即可。
额外注意
如果有多个字符串字段,每个字段都要绑定独立的回调上下文,避免内容互相覆盖。
内容的提问来源于stack exchange,提问作者Sergio Giménez
相关产品推荐
相关产品推荐

