You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 16:18:03