NanoPB : Comment gérer les champs répétés (tableaux) en C++
Voir aussi : Version C : Comment gérer les champs répétés/tableaux en C
NanoPB est une implémentation de Protocol Buffers optimisée pour la taille du code, destinée aux systèmes embarqués. Ce post montre comment gérer les champs répétés (tableaux) 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 RepeatedMessage {
repeated uint32 values = 1;
repeated float temperatures = 2;
}Génération du code NanoPB
Générez le code NanoPB avec un fichier .options pour spécifier les tailles des tableaux :
Créez repeated.options :
example.RepeatedMessage.values max_count:10Générez ensuite :
protoc --nanopb_out=. repeated.protoCela générera repeated.pb.h et repeated.pb.c.
Exemple C++ avec tableaux de taille fixe
Voici un exemple C++ complet utilisant des tableaux de taille fixe :
#include <stdio.h>
#include "repeated.pb.h"
#include "pb_encode.h"
#include "pb_decode.h"
int main() {
// Tampon pour le message encodé
uint8_t buffer[256];
size_t message_length;
// --- ENCODAGE ---
example_RepeatedMessage message = example_RepeatedMessage_init_zero;
// Définir les valeurs des champs répétés
message.values[0] = 10;
message.values[1] = 20;
message.values[2] = 30;
message.values_count = 3; // Important : définir le compte
message.temperatures[0] = 20.5f;
message.temperatures[1] = 21.0f;
message.temperatures[2] = 21.5f;
message.temperatures_count = 3; // Important : définir le compte
// Créer le flux pour l'encodage
pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
// Encoder le message
if (!pb_encode(&ostream, example_RepeatedMessage_fields, &message)) {
printf("Échec de l'encodage : %s\n", PB_GET_ERROR(&ostream));
return 1;
}
message_length = ostream.bytes_written;
printf("Encodé %zu octets\n", message_length);
// Afficher le dump hexadécimal des données encodées
printf("Données encodées : ");
for (size_t i = 0; i < message_length; i++) {
printf("%02x ", buffer[i]);
}
printf("\n");
// --- DÉCODAGE ---
example_RepeatedMessage decoded = example_RepeatedMessage_init_zero;
// Créer le flux pour le décodage
pb_istream_t istream = pb_istream_from_buffer(buffer, message_length);
// Décoder le message
if (!pb_decode(&istream, example_RepeatedMessage_fields, &decoded)) {
printf("Échec du décodage : %s\n", PB_GET_ERROR(&istream));
return 1;
}
// Afficher les valeurs décodées
printf("Valeurs décodées :\n");
printf(" values (%zu éléments) : ", decoded.values_count);
for (size_t i = 0; i < decoded.values_count; i++) {
printf("%u ", decoded.values[i]);
}
printf("\n");
printf(" temperatures (%zu éléments) : ", decoded.temperatures_count);
for (size_t i = 0; i < decoded.temperatures_count; i++) {
printf("%.1f ", decoded.temperatures[i]);
}
printf("\n");
return 0;
}Commande de compilation
Compilez l’exemple avec nanopb. NanoPB est généralement utilisé en incluant directement les fichiers source dans votre projet :
g++ -o repeated_example repeated_example.cpp repeated.pb.c pb_common.c pb_encode.c pb_decode.c -I.Remarque : Les fichiers source 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.
Script de test Python
Pour vérifier l’encodage, vous pouvez utiliser la bibliothèque protobuf de Python :
import repeated_pb2
# Lire les données binaires
with open('encoded.bin', 'rb') as f:
data = f.read()
# Décoder
msg = repeated_pb2.RepeatedMessage()
msg.ParseFromString(data)
print("Valeurs décodées par Python :")
print(f" values : {list(msg.values)}")
print(f" temperatures : {list(msg.temperatures)}")Compilez d’abord les définitions protobuf Python :
protoc --python_out=. repeated.protoModifiez ensuite l’exemple C++ pour enregistrer les données encodées dans un fichier :
// Après l'encodage, ajoutez ceci :
FILE *f = fopen("encoded.bin", "wb");
fwrite(buffer, 1, message_length, f);
fclose(f);Alternative : Champs répétés basés sur des callbacks
Pour une gestion dynamique des tableaux, vous pouvez utiliser des callbacks. Créez repeated_callback.options :
# Utiliser un callback pour les tableaux dynamiques
msg.RepeatedMessage.values callback
msg.RepeatedMessage.temperatures callbackRégénérez ensuite et utilisez cette approche :
#include <stdio.h>
#include <vector>
#include "repeated.pb.h"
#include "pb_encode.h"
#include "pb_decode.h"
// Callback d'encodage pour le tableau uint32
bool uint32_array_encode_callback(pb_ostream_t *stream, const pb_field_t *field, void * const *arg) {
const std::vector<uint32_t>* arr = (const std::vector<uint32_t>*)*arg;
for (uint32_t value : *arr) {
if (!pb_encode_tag_for_field(stream, field))
return false;
if (!pb_encode_varint(stream, value))
return false;
}
return true;
}
// Callback de décodage pour le tableau uint32
bool uint32_array_decode_callback(pb_istream_t *stream, const pb_field_t *field, void **arg) {
std::vector<uint32_t>* arr = (std::vector<uint32_t>*)*arg;
uint64_t value;
if (!pb_decode_varint(stream, &value))
return false;
arr->push_back((uint32_t)value);
return true;
}
// Callback d'encodage pour le tableau float
bool float_array_encode_callback(pb_ostream_t *stream, const pb_field_t *field, void * const *arg) {
const std::vector<float>* arr = (const std::vector<float>*)*arg;
for (float value : *arr) {
if (!pb_encode_tag_for_field(stream, field))
return false;
// Encoder le float comme 4 octets
union { float f; uint32_t u; } u;
u.f = value;
if (!pb_encode_varint(stream, u.u))
return false;
}
return true;
}
// Callback de décodage pour le tableau float
bool float_array_decode_callback(pb_istream_t *stream, const pb_field_t *field, void **arg) {
std::vector<float>* arr = (std::vector<float>*)*arg;
uint64_t value;
if (!pb_decode_varint(stream, &value))
return false;
union { float f; uint32_t u; } u;
u.u = (uint32_t)value;
arr->push_back(u.f);
return true;
}
int main() {
uint8_t buffer[256];
size_t message_length;
std::vector<uint32_t> values = {10, 20, 30};
std::vector<float> temperatures = {20.5f, 21.0f, 21.5f};
// --- ENCODAGE ---
example_RepeatedMessage message = example_RepeatedMessage_init_zero;
message.values.funcs.encode = uint32_array_encode_callback;
message.values.arg = &values;
message.temperatures.funcs.encode = float_array_encode_callback;
message.temperatures.arg = &temperatures;
pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
if (!pb_encode(&ostream, example_RepeatedMessage_fields, &message)) {
printf("Échec de l'encodage : %s\n", PB_GET_ERROR(&ostream));
return 1;
}
message_length = ostream.bytes_written;
printf("Encodé %zu octets\n", message_length);
// --- DÉCODAGE ---
example_RepeatedMessage decoded = example_RepeatedMessage_init_zero;
std::vector<uint32_t> decoded_values;
std::vector<float> decoded_temperatures;
decoded.values.funcs.decode = uint32_array_decode_callback;
decoded.values.arg = &decoded_values;
decoded.temperatures.funcs.decode = float_array_decode_callback;
decoded.temperatures.arg = &decoded_temperatures;
pb_istream_t istream = pb_istream_from_buffer(buffer, message_length);
if (!pb_decode(&istream, example_RepeatedMessage_fields, &decoded)) {
printf("Échec du décodage : %s\n", PB_GET_ERROR(&istream));
return 1;
}
printf("Valeurs décodées :\n");
printf(" values (%zu éléments) : ", decoded_values.size());
for (uint32_t value : decoded_values) {
printf("%u ", value);
}
printf("\n");
printf(" temperatures (%zu éléments) : ", decoded_temperatures.size());
for (float temp : decoded_temperatures) {
printf("%.1f ", temp);
}
printf("\n");
return 0;
}Points clés
- Tableaux de taille fixe : Utilisez
max_countdans le fichier .options pour une allocation statique simple - Basé sur des callbacks : Utilisez
callbackdans le fichier .options pour une gestion dynamique des tableaux - Taille fixe : Définissez le champ
*_countpour spécifier le nombre d’éléments - Basé sur des callbacks : Implémentez des callbacks d’encodage/décodage pour une allocation dynamique
- Les tableaux sont encodés comme des tags de champs répétés avec des valeurs
- Utilisez
std::vector<T>en C++ pour la gestion dynamique des tableaux - Vérifiez toujours les comptes de tableau pour éviter les dépassements
Quand utiliser quelle approche
- Tableaux de taille fixe : Quand vous connaissez la taille maximale du tableau et que vous voulez un code simple
- Basé sur des callbacks : Quand la taille du tableau est variable ou que vous avez besoin d’une allocation dynamique de mémoire
Sortie attendue
Encoded 15 bytes
Encoded data: 08 0a 08 14 08 1e 15 00 00 a4 41 15 00 00 a8 41 15 00 00 ac 41
Decoded values:
values (3 items): 10 20 30
temperatures (3 items): 20.5 21.0 21.5 Plus de posts 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
- 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 tableaux personnalisés en C++
- Convertisseurs de tableaux personnalisés en C