NanoPB : Convertisseurs de tableau personnalisés avec offset et taille en C
Voir aussi : Version C++ : Comment gérer les convertisseurs de tableau personnalisés en C++
NanoPB est une implémentation de Protocol Buffers optimisée pour la taille du code, destinée aux systèmes embarqués. Cet article montre comment gérer les convertisseurs de tableau personnalisés en C avec NanoPB.
Définition proto
Créez d’abord un fichier .proto avec des champs répétés :
syntax = "proto3";
package example;
message CustomArrayMessage {
repeated uint32 values = 1;
}Générer le code NanoPB
Générez le code NanoPB avec un fichier .options pour spécifier le nombre maximal :
Créez custom_array.options :
example.CustomArrayMessage.values max_count:20Puis générez :
protoc --nanopb_out=. custom_array.protoCela générera custom_array.pb.h et custom_array.pb.c.
Exemple en C avec convertisseur de tableau personnalisé
Voici un exemple complet en C implémentant un convertisseur de tableau personnalisé avec offset et taille :
#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"
// Contexte du convertisseur de tableau personnalisé
typedef struct {
const uint32_t* data;
size_t size;
size_t offset;
size_t count;
} array_context_t;
// Callback d'encodage pour tableau uint32 avec offset et taille
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() {
// Tampon pour le message encodé
uint8_t buffer[256];
size_t message_length;
// Créer un tableau avec 10 éléments
uint32_t values[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// --- ENCODAGE ---
example_CustomArrayMessage message = example_CustomArrayMessage_init_zero;
// Configurer le contexte : encoder les éléments 3-7 (offset=2, count=5)
array_context_t ctx = {
.data = values,
.size = 10,
.offset = 2,
.count = 5
};
// Configurer le callback
message.values.funcs.encode = uint32_array_encode_callback;
message.values.arg = &ctx;
// Créer un flux pour l'encodage
pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
// Encoder le message
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);
// Afficher le dump hexadécimal
printf("Encoded data: ");
for (size_t i = 0; i < message_length; i++) {
printf("%02x ", buffer[i]);
}
printf("\n");
// Afficher ce qui a été encodé
printf("Encoded values: ");
for (size_t i = 2; i < 2 + 5 && i < 10; i++) {
printf("%u ", values[i]);
}
printf("\n");
return 0;
}Commande de compilation
Compilez l’exemple avec nanopb. NanoPB est généralement utilisé en incluant directement les fichiers sources dans votre projet :
gcc -o custom_array_example custom_array_example.c custom_array.pb.c pb_common.c pb_encode.c pb_decode.c -I.Remarque : Les fichiers sources NanoPB (pb_common.c, pb_encode.c, pb_decode.c) doivent être compilés directement avec votre projet. Vous pouvez les obtenir depuis le dépôt GitHub NanoPB.
Points clés
- Structure de contexte : Utilisez une struct pour passer l’offset, le count et le pointeur de données
- Logique d’offset : Commencer l’encodage à l’offset spécifié
- Limite de count : Encoder au plus count éléments après l’offset
- Modèle de callback : Utiliser pb_callback_t avec une fonction d’encodage personnalisée
- Flexibilité : Peut encoder n’importe quelle tranche d’un tableau sans copie
- Efficacité mémoire : Pas besoin de créer des tableaux temporaires
- Modèle KKS-Firmware : Basé sur ArrayConverterWithOffsetAndSize de KKS-Firmware
Quand utiliser les convertisseurs de tableau personnalisés
- Lorsque vous devez encoder un sous-ensemble d’un grand tableau
- Lorsque vous souhaitez éviter de copier des données
- Lors de l’implémentation de pagination ou de découpage en blocs
- Lorsque la disposition du tableau ne correspond pas aux exigences protobuf
- Lorsque vous avez besoin d’une logique d’encodage personnalisée pour les tableaux
Sortie attendue
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 Notez que seuls les éléments 3, 4, 5, 6, 7 sont encodés (5 éléments à partir de l’offset 2).
Avancé : Implémentation générique de type template
Pour une implémentation C plus générique (simulant les templates) :
#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;
}Utilisation réelle
Ce modèle est utilisé dans KKS-Firmware pour :
- Encoder uniquement les canaux actifs d’un tableau de canaux plus grand
- Envoyer des données partielles de capteurs pour économiser de la bande passante
- Implémenter des mises à jour différentielles
- Gérer efficacement les tableaux épars
Différences par rapport au C++
La version C diffère de la version C++ de plusieurs manières :
- Pas de templates : Utilisez des structs et des pointeurs de fonction à la place
- Gestion manuelle du contexte : Doit passer le contexte explicitement
- Pas de std::array : Utilisez des tableaux C simples avec suivi de taille
- Pas de std::algorithm : Implémentez l’itération manuellement
- Même modèle de callback : Les deux utilisent pb_callback_t
Plus d’articles sur NanoPB
- Types scalaires de base en C++
- Types scalaires de base en C
- Types chaîne en C++
- Types chaîne en C
- Types bytes en C++
- Types bytes en C
- Champs optionnels en C++
- Champs optionnels en C
- Champs répétés/tableaux en C++
- Champs répétés/tableaux en C
- Enums en C++
- Enums en C
- Messages imbriqués en C++
- Messages imbriqués en C
- Types oneof/union en C++
- Types oneof/union en C
- Convertisseurs de tableau personnalisés en C++