関連記事: C 版: C でカスタム配列コンバータを扱う方法
NanoPB は組み込みシステム向けにコードサイズを最適化した Protocol Buffers 実装です。この記事では、KKS-Firmware の実装に基づいて、NanoPB で C++ のカスタム配列コンバータを扱う方法を説明します。
Proto 定義
まず、リピートフィールドを持つ .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>;
// コールバックを設定
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);
// 16進ダンプを出力
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 個の要素をエンコードします
- コールバックパターン: カスタムエンコード/デコード関数で 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++ Protocol Buffers
この記事が役に立った場合は、TechOverflow での新しい記事の調査と公開を支援するため、「Buy Me a Coffee」でのご支援、または PayPal 経由でのご寄付をご検討ください