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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 11:23:16