NanoPB: Comment gérer les messages imbriqués en C
Voir aussi : Version C++ : Comment gérer les messages imbriqué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 messages imbriqués en C avec NanoPB.
Définition Proto
Créez d’abord un fichier .proto avec des messages imbriqués :
syntax = "proto3";
package example;
message Address {
string street = 1;
string city = 2;
string country = 3;
}
message Person {
string name = 1;
uint32 age = 2;
Address address = 3;
}Générer le code NanoPB
Générez le code NanoPB avec un fichier .options pour spécifier les tailles des tampons de chaîne :
Créez nested.options :
example.Address.street max_size:64
example.Address.city max_size:32
example.Address.country max_size:32
example.Person.name max_size:64Générez ensuite :
protoc --nanopb_out=. nested.protoCela générera nested.pb.h et nested.pb.c.
Exemple C
Voici un exemple C complet gérant les messages imbriqués :
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "nested.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_Person person = example_Person_init_zero;
// Définir les valeurs du message imbriqué
strncpy(person.name, "John Doe", sizeof(person.name) - 1);
person.has_address = true;
strncpy(person.address.street, "123 Main St", sizeof(person.address.street) - 1);
strncpy(person.address.city, "Springfield", sizeof(person.address.city) - 1);
strncpy(person.address.country, "USA", sizeof(person.address.country) - 1);
// 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_Person_fields, &person)) {
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_Person decoded = example_Person_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_Person_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(" name : %s\n", decoded.name);
printf(" age : %u\n", (unsigned int)decoded.age);
printf(" address :\n");
printf(" street : %s\n", decoded.address.street);
printf(" city : %s\n", decoded.address.city);
printf(" country : %s\n", decoded.address.country);
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 nested_example nested_example.c nested.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 de NanoPB.
Script de test Python
Pour vérifier l’encodage, vous pouvez utiliser la bibliothèque protobuf de Python :
import nested_pb2
# Lire les données binaires
with open('encoded.bin', 'rb') as f:
data = f.read()
# Décoder
msg = nested_pb2.Person()
msg.ParseFromString(data)
print("Valeurs décodées par Python :")
print(f" name : {msg.name}")
print(f" age : {msg.age}")
print(f" address :")
print(f" street : {msg.address.street}")
print(f" city : {msg.address.city}")
print(f" country : {msg.address.country}")Compilez d’abord les définitions protobuf Python :
protoc --python_out=. nested.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);Exemple avec message imbriqué optionnel
Voici un exemple avec un message imbriqué optionnel :
syntax = "proto3";
package example;
message Address {
string street = 1;
string city = 2;
}
message Person {
string name = 1;
optional Address address = 2;
}Créez nested_optional.options :
example.Address.street max_size:64
example.Address.city max_size:32
example.Person.name max_size:64#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "nested_optional.pb.h"
#include "pb_encode.h"
#include "pb_decode.h"
int main() {
uint8_t buffer[256];
size_t message_length;
// --- ENCODAGE ---
example_Person person = example_Person_init_zero;
strncpy(person.name, "Jane Doe", sizeof(person.name) - 1);
// Définir le message imbriqué optionnel
strncpy(person.address.street, "456 Oak Ave", sizeof(person.address.street) - 1);
strncpy(person.address.city, "Boston", sizeof(person.address.city) - 1);
person.has_address = true; // Important : définir le drapeau has_*
pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
if (!pb_encode(&ostream, example_Person_fields, &person)) {
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_Person decoded = example_Person_init_zero;
pb_istream_t istream = pb_istream_from_buffer(buffer, message_length);
if (!pb_decode(&istream, example_Person_fields, &decoded)) {
printf("Échec du décodage : %s\n", PB_GET_ERROR(&istream));
return 1;
}
printf("Valeurs décodées :\n");
printf(" name : %s\n", decoded.name);
if (decoded.has_address) {
printf(" address :\n");
printf(" street : %s\n", decoded.address.street);
printf(" city : %s\n", decoded.address.city);
} else {
printf(" address : (non défini)\n");
}
return 0;
}Points clés
- Messages imbriqués : Les messages peuvent contenir d’autres messages en tant que champs
- Initialisation : Utilisez
*_init_zeropour initialiser à la fois les messages externes et imbriqués - Encodage : Définissez directement les champs du message imbriqué, puis encodez le message externe
- Décodage : Décodez le message externe, les messages imbriqués sont décodés automatiquement
- Imbriqué optionnel : Utilisez le mot-clé
optionalet définissez le drapeauhas_* - Structure : Les messages imbriqués offrent une organisation hiérarchique des données
- Tailles de tampon : Spécifiez les tailles de tampon pour tous les champs chaîne dans le fichier .options
Quand utiliser les messages imbriqués
- Lorsque vous avez des données hiérarchiques ou groupées
- Lorsque vous voulez réutiliser des définitions de messages
- Lorsque vous avez besoin d’organiser des champs liés ensemble
- Lorsque vous voulez améliorer la lisibilité des messages
- Lorsque vous avez des structures de données complexes
Sortie attendue
Encoded 35 bytes
Encoded data: 0a 08 4a 6f 68 6e 20 44 6f 65 10 1e 1a 1b 0a 0b 31 32 33 20 4d 61 69 6e 20 53 74 12 08 4e 65 77 20 59 6f 72 6b 1a 03 55 53 41
Decoded values:
name: John Doe
age: 30
address:
street: 123 Main St
city: New York
country: USASortie attendue (imbriqué optionnel)
Encoded 27 bytes
Decoded values:
name: Jane Doe
address:
street: 456 Oak Ave
city: BostonDifférences par rapport au C++
La version C est presque identique à la version C++, avec des différences clés :
- Utilisez un cast explicite pour les spécificateurs de format printf en C
- Aucune fonctionnalité spécifique au C++ n’est nécessaire pour la gestion des messages imbriqués
- Le code généré est identique pour les deux langages
- La gestion de la mémoire est la même (allocation statique dans les deux cas)
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++
- Types oneof/union en C++
- Types oneof/union en C
- Convertisseurs de tableaux personnalisés en C++
- Convertisseurs de tableaux personnalisés en C