NanoPB: Як обробляти користувацькі конвертери масивів в C
Див. також: C++ version: How to handle custom array converters in C++
NanoPB — це оптимізована за розміром коду реалізація Protocol Buffers для вбудованих систем. Ця публікація показує, як обробляти користувацькі конвертери масивів у C за допомогою NanoPB.
Визначення Proto
Спочатку створіть файл .proto з повторюваними полями:
syntax = "proto3";
package example;
message CustomArrayMessage {
repeated uint32 values = 1;
}Генерація коду NanoPB
Згенеруйте код NanoPB з файлом .options, щоб вказати максимальну кількість:
Створіть custom_array.options:
example.CustomArrayMessage.values max_count:20Потім згенеруйте:
protoc --nanopb_out=. custom_array.protoЦе згенерує custom_array.pb.h та custom_array.pb.c.
Приклад на C з користувацьким конвертером масиву
Ось повний приклад на C, що реалізує користувацький конвертер масиву зі зсувом та розміром:
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <limits.h>
#include "custom_array.pb.h"
#include "pb_encode.h"
#include "pb_decode.h"
// Контекст користувацького конвертера масиву
typedef struct {
const uint32_t* data;
size_t size;
size_t offset;
size_t count;
} array_context_t;
// Зворотний виклик кодування для масиву uint32 зі зсувом та розміром
bool uint32_array_encode_callback(pb_ostream_t *stream, const pb_field_t *field, void * const *arg) {
const array_context_t* ctx = (const array_context_t*)*arg;
size_t start = ctx->offset;
size_t end = (ctx->offset + ctx->count < ctx->size) ?
ctx->offset + ctx->count : ctx->size;
for (size_t i = start; i < end; i++) {
if (!pb_encode_tag_for_field(stream, field))
return false;
if (!pb_encode_varint(stream, ctx->data[i]))
return false;
}
return true;
}
int main() {
// Буфер для закодованого повідомлення
uint8_t buffer[256];
size_t message_length;
// Створення масиву з 10 елементів
uint32_t values[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// --- КОДУВАННЯ ---
example_CustomArrayMessage message = example_CustomArrayMessage_init_zero;
// Налаштування контексту: кодувати елементи 3-7 (offset=2, count=5)
array_context_t ctx = {
.data = values,
.size = 10,
.offset = 2,
.count = 5
};
// Налаштування зворотного виклику
message.values.funcs.encode = uint32_array_encode_callback;
message.values.arg = &ctx;
// Створення потоку для кодування
pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
// Кодування повідомлення
if (!pb_encode(&ostream, example_CustomArrayMessage_fields, &message)) {
printf("Помилка кодування: %s\n", PB_GET_ERROR(&ostream));
return 1;
}
message_length = ostream.bytes_written;
printf("Закодовано %zu байт (елементи 3-7)\n", message_length);
// Друк шістнадцяткового дампу
printf("Закодовані дані: ");
for (size_t i = 0; i < message_length; i++) {
printf("%02x ", buffer[i]);
}
printf("\n");
// Друк того, що було закодовано
printf("Закодовані значення: ");
for (size_t i = 2; i < 2 + 5 && i < 10; i++) {
printf("%u ", values[i]);
}
printf("\n");
return 0;
}Команда компіляції
Скомпілюйте приклад з nanopb. NanoPB зазвичай використовується шляхом безпосереднього включення вихідних файлів у ваш проєкт:
gcc -o custom_array_example custom_array_example.c 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.
Ключові моменти
- Структура контексту: Використовуйте структуру для передачі зсуву, кількості та вказівника на дані
- Логіка зсуву: Початок кодування з вказаного зсуву
- Обмеження кількості: Кодувати не більше ніж count елементів після зсуву
- Патерн зворотного виклику: Використовуйте pb_callback_t з користувацькою функцією кодування
- Гнучкість: Можна кодувати будь-який зріз масиву без копіювання
- Ефективність пам’яті: Не потрібно створювати тимчасові масиви
- Патерн KKS-Firmware: Базується на ArrayConverterWithOffsetAndSize з KKS-Firmware
Коли використовувати користувацькі конвертери масивів
- Коли потрібно закодувати підмножину великого масиву
- Коли ви хочете уникнути копіювання даних
- Коли реалізуєте пагінацію або чанкінг
- Коли структура масиву не відповідає вимогам protobuf
- Коли потрібна користувацька логіка кодування для масивів
Очікуваний вивід
Закодовано 10 байт (елементи 3-7)
Закодовані дані: 08 03 08 04 08 05 08 06 08 07
Закодовані значення: 3 4 5 6 7 Зверніть увагу, що кодуються лише елементи 3, 4, 5, 6, 7 (5 елементів, починаючи зі зсуву 2).
Додатково: Узагальнена реалізація у стилі шаблонів
Для більш узагальненої реалізації на C (імітація шаблонів):
#include <stddef.h>
#include <limits.h>
#define MAX_OFFSET_SIZE (SIZE_MAX / 2)
typedef struct {
const void* data;
size_t element_size;
size_t size;
size_t offset;
size_t count;
bool (*encode_element)(pb_ostream_t*, const pb_field_t*, const void*);
} generic_array_context_t;
bool generic_array_encode_callback(pb_ostream_t *stream, const pb_field_t *field, void * const *arg) {
const generic_array_context_t* ctx = (const generic_array_context_t*)*arg;
size_t start = ctx->offset;
size_t end = (ctx->offset + ctx->count < ctx->size) ?
ctx->offset + ctx->count : ctx->size;
const uint8_t* data = (const uint8_t*)ctx->data;
for (size_t i = start; i < end; i++) {
const void* element = data + (i * ctx->element_size);
if (!ctx->encode_element(stream, field, element)) {
return false;
}
}
return true;
}Реальне використання
Цей патерн використовується в KKS-Firmware для:
- Кодування лише активних каналів з більшого масиву каналів
- Надсилання часткових даних датчиків для економії пропускної здатності
- Реалізації диференційних оновлень
- Ефективної обробки розріджених масивів
Відмінності від C++
Версія на C відрізняється від C++ кількома способами:
- Немає шаблонів: Використовуються структури та вказівники на функції натомість
- Ручне керування контекстом: Контекст потрібно передавати явно
- Немає std::array: Використовуються звичайні C-масиви з відстеженням розміру
- Немає std::algorithm: Ітерацію реалізовано вручну
- Той самий патерн зворотного виклику: Обидві використовують pb_callback_t
More NanoPB posts
- Basic scalar types in C++
- Basic scalar types in C
- String types in C++
- String types in C
- Bytes types in C++
- Bytes types in C
- Optional fields in C++
- Optional fields in C
- Repeated fields/arrays in C++
- Repeated fields/arrays in C
- Enums in C++
- Enums in C
- Nested messages in C++
- Nested messages in C
- Oneof/union types in C++
- Oneof/union types in C
- Custom array converters in C++