NanoPB: Benutzerdefinierte Array-Konverter mit Offset und Größe in C
Siehe auch: C++-Version: Umgang mit benutzerdefinierten Array-Konvertern in C++
NanoPB ist eine codegrößenoptimierte Protocol-Buffers-Implementierung für eingebettete Systeme. Dieser Beitrag zeigt, wie man benutzerdefinierte Array-Konverter in C mit NanoPB handhabt.
Proto-Definition
Erstelle zuerst eine .proto-Datei mit wiederholten Feldern:
syntax = "proto3";
package example;
message CustomArrayMessage {
repeated uint32 values = 1;
}NanoPB-Code generieren
Generiere den NanoPB-Code mit einer .options-Datei, um die maximale Anzahl anzugeben:
Erstelle custom_array.options:
example.CustomArrayMessage.values max_count:20Dann generieren:
protoc --nanopb_out=. custom_array.protoDies generiert custom_array.pb.h und custom_array.pb.c.
C-Beispiel mit benutzerdefiniertem Array-Konverter
Hier ist ein vollständiges C-Beispiel, das einen benutzerdefinierten Array-Konverter mit Offset und Größe implementiert:
#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"
// Benutzerdefinierter Array-Konverter-Kontext
typedef struct {
const uint32_t* data;
size_t size;
size_t offset;
size_t count;
} array_context_t;
// Kodierungs-Callback für uint32-Array mit Offset und Größe
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() {
// Puffer für kodierte Nachricht
uint8_t buffer[256];
size_t message_length;
// Array mit 10 Elementen erstellen
uint32_t values[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// --- KODIERUNG ---
example_CustomArrayMessage message = example_CustomArrayMessage_init_zero;
// Kontext einrichten: Elemente 3-7 kodieren (offset=2, count=5)
array_context_t ctx = {
.data = values,
.size = 10,
.offset = 2,
.count = 5
};
// Callback einrichten
message.values.funcs.encode = uint32_array_encode_callback;
message.values.arg = &ctx;
// Strom für Kodierung erstellen
pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
// Nachricht kodieren
if (!pb_encode(&ostream, example_CustomArrayMessage_fields, &message)) {
printf("Kodierung fehlgeschlagen: %s\n", PB_GET_ERROR(&ostream));
return 1;
}
message_length = ostream.bytes_written;
printf("%zu Bytes kodiert (Elemente 3-7)\n", message_length);
// Hex-Dump ausgeben
printf("Kodierte Daten: ");
for (size_t i = 0; i < message_length; i++) {
printf("%02x ", buffer[i]);
}
printf("\n");
// Ausgeben was kodiert wurde
printf("Kodierte Werte: ");
for (size_t i = 2; i < 2 + 5 && i < 10; i++) {
printf("%u ", values[i]);
}
printf("\n");
return 0;
}Kompilierungsbefehl
Kompiliere das Beispiel mit NanoPB. NanoPB wird typischerweise verwendet, indem die Quelldateien direkt in dein Projekt eingebunden werden:
gcc -o custom_array_example custom_array_example.c custom_array.pb.c pb_common.c pb_encode.c pb_decode.c -I.Hinweis: NanoPB-Quelldateien (pb_common.c, pb_encode.c, pb_decode.c) müssen direkt mit deinem Projekt kompiliert werden. Du kannst diese aus dem NanoPB GitHub-Repository beziehen.
Wichtige Punkte
- Kontextstruktur: Verwende ein struct, um Offset, Anzahl und Datenzeiger zu übergeben
- Offset-Logik: Beginne die Kodierung am angegebenen Offset
- Anzahlbegrenzung: Kodiere höchstens Anzahl Elemente nach dem Offset
- Callback-Muster: Verwende pb_callback_t mit benutzerdefinierter Kodierungsfunktion
- Flexibilität: Kann jeden Ausschnitt eines Arrays kodieren ohne zu kopieren
- Speichereffizienz: Keine Notwendigkeit, temporäre Arrays zu erstellen
- KKS-Firmware-Muster: Basiert auf ArrayConverterWithOffsetAndSize aus KKS-Firmware
Wann benutzerdefinierte Array-Konverter verwendet werden sollten
- Wenn du eine Teilmenge eines großen Arrays kodieren musst
- Wenn du das Kopieren von Daten vermeiden möchtest
- Wenn du Paginierung oder Chunking implementieren
- Wenn das Array-Layout nicht den Protobuf-Anforderungen entspricht
- Wenn du benutzerdefinierte Kodierungslogik für Arrays benötigst
Erwartete Ausgabe
10 Bytes kodiert (Elemente 3-7)
Kodierte Daten: 08 03 08 04 08 05 08 06 08 07
Kodierte Werte: 3 4 5 6 7 Beachte, dass nur die Elemente 3, 4, 5, 6, 7 kodiert werden (5 Elemente beginnend bei Offset 2).
Erweitert: Generische template-ähnliche Implementierung
Für eine generischere C-Implementierung (Templates simulierend):
#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;
}Praxisnahe Verwendung
Dieses Muster wird in KKS-Firmware verwendet für:
- Kodierung nur aktiver Kanäle aus einem größeren Kanal-Array
- Senden partieller Sensordaten zur Bandbreiteneinsparung
- Implementierung differentieller Updates
- Effiziente Handhabung spärlicher Arrays
Unterschiede zu C++
Die C-Version unterscheidet sich in mehreren Punkten von C++:
- Keine Templates: Verwende structs und Funktionszeiger stattdessen
- Manuelle Kontextverwaltung: Muss Kontext explizit übergeben
- Kein std::array: Verwende einfache C-Arrays mit Größenverfolgung
- Kein std::algorithm: Iteration manuell implementieren
- Gleiches Callback-Muster: Beide verwenden pb_callback_t
Weitere NanoPB-Beiträge
- Grundlegende skalare Typen in C++
- Grundlegende skalare Typen in C
- String-Typen in C++
- String-Typen in C
- Bytes-Typen in C++
- Bytes-Typen in C
- Optionale Felder in C++
- Optionale Felder in C
- Wiederholte Felder/Arrays in C++
- Wiederholte Felder/Arrays in C
- Enums in C++
- Enums in C
- Verschachtelte Nachrichten in C++
- Verschachtelte Nachrichten in C
- Oneof/Union-Typen in C++
- Oneof/Union-Typen in C
- Benutzerdefinierte Array-Konverter in C++