NanoPB:在 C++ 中使用偏移和大小的自定义数组转换器
NanoPB 是一个面向嵌入式系统的代码体积优化的 Protocol Buffers 实现。本文介绍如何在 C++ 中使用 NanoPB 处理自定义数组转换器,基于 KKS-Firmware 的实现。
Proto 定义
首先,创建一个包含 repeated 字段的 .proto 文件:
custom_array.proto
syntax = "proto3";
package example;
message CustomArrayMessage {
repeated uint32 values = 1;
}生成 NanoPB 代码
使用 .options 文件指定最大计数,然后生成 NanoPB 代码:
创建 custom_array.options:
custom_array.options
example.CustomArrayMessage.values max_count:20然后生成:
generate_nanopb_custom_array.sh
protoc --nanopb_out=. custom_array.proto这将生成 custom_array.pb.h 和 custom_array.pb.c。
使用自定义数组转换器的 C++ 示例
下面是一个实现带偏移和大小的自定义数组转换器的完整 C++ 示例:
custom_array_example.cpp
#include <stdio.h>
#include <algorithm>
#include <array>
#include <limits>
#include "custom_array.pb.h"
#include "pb_encode.h"
#include "pb_decode.h"
// 带偏移和大小模板参数的自定义数组转换器
template<class ITEM_CONVERTER, class CONTAINER, size_t OFFSET=0, size_t SIZE=std::numeric_limits<size_t>::max()/2>
class ArrayConverterWithOffsetAndSize {
public:
static bool encodeCallback(pb_ostream_t *stream, const pb_field_t *field, const CONTAINER &container) {
size_t i = 0;
// 使用 for 循环遍历容器
for (const auto &item : container) {
i++; // 我们检查的第一个索引是 1
if(i <= OFFSET) { // <=,不是"<",因为我们在这一行之前执行了 ++
continue;
}
if(i > OFFSET + SIZE) {
break;
}
// 编码元素
if (!ITEM_CONVERTER::encodeCallback(stream, field, item)) {
return false;
}
}
return true;
}
static bool decodeCallback(pb_istream_t *stream, const pb_field_t *field, CONTAINER &container) {
// 将元素添加到容器
// 注意:这是一个简化示例 - 实际实现取决于容器类型
return false; // 此示例中未实现
}
};
// 简单的 uint32 转换器
class UInt32Converter {
public:
static bool encodeCallback(pb_ostream_t *stream, const pb_field_t *field, uint32_t value) {
if (!pb_encode_tag_for_field(stream, field))
return false;
return pb_encode_varint(stream, value);
}
};
int main() {
// 编码消息的缓冲区
uint8_t buffer[256];
size_t message_length;
// 创建包含 10 个元素的数组
std::array<uint32_t, 10> values = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// --- 编码 ---
example_CustomArrayMessage message = example_CustomArrayMessage_init_zero;
// 使用自定义转换器:编码第 3-7 个元素(OFFSET=2, SIZE=5)
using CustomConverter = ArrayConverterWithOffsetAndSize<UInt32Converter, std::array<uint32_t, 10>, 2, 5>;
// 设置 callback
message.values.funcs.encode = [](pb_ostream_t *stream, const pb_field_t *field, void * const *arg) {
const std::array<uint32_t, 10>* container = (const std::array<uint32_t, 10>*)*arg;
return CustomConverter::encodeCallback(stream, field, *container);
};
message.values.arg = &values;
// 创建编码流
pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
// 编码消息
if (!pb_encode(&ostream, example_CustomArrayMessage_fields, &message)) {
printf("Encoding failed: %s\n", PB_GET_ERROR(&ostream));
return 1;
}
message_length = ostream.bytes_written;
printf("Encoded %zu bytes (elements 3-7)\n", message_length);
// 打印十六进制转储
printf("Encoded data: ");
for (size_t i = 0; i < message_length; i++) {
printf("%02x ", buffer[i]);
}
printf("\n");
// 打印编码的内容
printf("Encoded values: ");
for (size_t i = 2; i < 2 + 5 && i < values.size(); i++) {
printf("%u ", values[i]);
}
printf("\n");
return 0;
}编译命令
使用 nanopb 编译该示例。NanoPB 通常通过将源文件直接包含到你的项目来使用:
compile_custom_array_example.sh
g++ -o custom_array_example custom_array_example.cpp custom_array.pb.c pb_common.c pb_encode.c pb_decode.c -I.**注意:**NanoPB 源文件(pb_common.c、pb_encode.c、pb_decode.c)需要直接与你的项目一起编译。你可以从 NanoPB GitHub 仓库 获取这些文件。
要点
- 模板参数:OFFSET 和 SIZE 控制要编码哪些元素
- 偏移逻辑:跳过前 OFFSET 个元素(循环中使用 1 基索引)
- 大小限制:在偏移之后最多编码 SIZE 个元素
- Callback 模式:使用 pb_callback_t 配合自定义编码/解码函数
- 灵活性:无需复制即可编码数组的任意切片
- 内存效率:无需创建临时数组
- KKS-Firmware 模式:基于 KKS-Firmware 中的 ArrayConverterWithOffsetAndSize
何时使用自定义数组转换器
- 当你需要编码大型数组的子集时
- 当你想避免复制数据时
- 当实现分页或分块时
- 当数组布局不匹配 protobuf 要求时
- 当你需要数组的自定义编码逻辑时
预期输出
custom_array_expected_output.txt
Encoded 10 bytes (elements 3-7)
Encoded data: 08 03 08 04 08 05 08 06 08 07
Encoded values: 3 4 5 6 7 注意只有元素 3、4、5、6、7 被编码(从偏移 2 开始的 5 个元素)。
进阶:解码支持
要支持解码,扩展该转换器:
decode_callback_snippet.cpp
static bool decodeCallback(pb_istream_t *stream, const pb_field_t *field, CONTAINER &container) {
uint64_t value;
if (!pb_decode_varint(stream, &value))
return false;
// 添加到容器(实现取决于容器类型)
// 对于 std::array,你可能需要跟踪单独的索引
return true;
}实际应用
此模式在 KKS-Firmware 中用于:
- 仅编码更大通道数组中的活动通道
- 发送部分传感器数据以节省带宽
- 实现差分更新
- 高效处理稀疏数组
更多 NanoPB 文章
- C++ 中的基本标量类型
- C 中的基本标量类型
- C++ 中的字符串类型
- C 中的字符串类型
- C++ 中的 bytes 类型
- C 中的 bytes 类型
- C++ 中的可选字段
- C 中的可选字段
- C++ 中的 repeated 字段/数组
- C 中的 repeated 字段/数组
- C++ 中的枚举
- C 中的枚举
- C++ 中的嵌套消息
- C 中的嵌套消息
- C++ 中的 oneof/union 类型
- C 中的 oneof/union 类型
- C 中的自定义数组转换器
Check out similar posts by category:
Embedded, C/C++, Protocol Buffers
If this post helped you, please consider buying me a coffee or donating via PayPal to support research & publishing of new posts on TechOverflow