NanoPB: Comment gérer les types oneof (union) en C
Voir aussi : Version C++ : Comment gérer les types oneof/union 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 types oneof (union) en C avec NanoPB.
Définition Proto
Créez d’abord un fichier .proto avec des champs oneof :
syntax = "proto3";
package example;
message OneofMessage {
oneof value {
uint32 int_value = 1;
float float_value = 2;
string string_value = 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 oneof.options :
example.OneofMessage.string_value max_size:32Générez ensuite :
protoc --nanopb_out=. oneof.protoCela générera oneof.pb.h et oneof.pb.c.
Exemple C
Voici un exemple C complet gérant les champs oneof :
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "oneof.pb.h"
#include "pb_encode.h"
#include "pb_decode.h"
int main() {
// Tampon pour le message encodé
uint8_t buffer[128];
size_t message_length;
// --- ENCODAGE (int_value) ---
example_OneofMessage message = example_OneofMessage_init_zero;
// Définir le champ oneof (un seul peut être défini à la fois)
message.which_value = example_OneofMessage_int_value_tag;
message.value.int_value = 42;
// 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_OneofMessage_fields, &message)) {
printf("Échec de l'encodage : %s\n", PB_GET_ERROR(&ostream));
return 1;
}
message_length = ostream.bytes_written;
printf("Encodé %zu octets (int_value)\n", message_length);
// Afficher le dump hexadécimal
printf("Données encodées : ");
for (size_t i = 0; i < message_length; i++) {
printf("%02x ", buffer[i]);
}
printf("\n");
// --- DÉCODAGE ---
example_OneofMessage decoded = example_OneofMessage_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_OneofMessage_fields, &decoded)) {
printf("Échec du décodage : %s\n", PB_GET_ERROR(&istream));
return 1;
}
// Afficher la valeur décodée
printf("Valeur décodée :\n");
switch (decoded.which_value) {
case example_OneofMessage_int_value_tag:
printf(" int_value : %u\n", (unsigned int)decoded.value.int_value);
break;
case example_OneofMessage_float_value_tag:
printf(" float_value : %f\n", decoded.value.float_value);
break;
case example_OneofMessage_string_value_tag:
printf(" string_value : %s\n", decoded.value.string_value);
break;
default:
printf(" (aucun)\n");
break;
}
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 oneof_example oneof_example.c oneof.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 oneof_pb2
# Lire les données binaires
with open('encoded.bin', 'rb') as f:
data = f.read()
# Décoder
msg = oneof_pb2.OneofMessage()
msg.ParseFromString(data)
print("Valeurs décodées par Python :")
print(f" Which oneof : {msg.WhichOneof('value')}")
if msg.HasField('int_value'):
print(f" int_value : {msg.int_value}")
if msg.HasField('float_value'):
print(f" float_value : {msg.float_value}")
if msg.HasField('string_value'):
print(f" string_value : {msg.string_value}")Compilez d’abord les définitions protobuf Python :
protoc --python_out=. oneof.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 tous les types oneof
Voici un exemple montrant tous les types oneof :
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "oneof.pb.h"
#include "pb_encode.h"
#include "pb_decode.h"
void encode_decode_int_value() {
uint8_t buffer[128];
size_t message_length;
example_OneofMessage message = example_OneofMessage_init_zero;
message.which_value = example_OneofMessage_int_value_tag;
message.value.int_value = 42;
pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
if (!pb_encode(&ostream, example_OneofMessage_fields, &message)) {
printf("Échec de l'encodage\n");
return;
}
message_length = ostream.bytes_written;
example_OneofMessage decoded = example_OneofMessage_init_zero;
pb_istream_t istream = pb_istream_from_buffer(buffer, message_length);
if (!pb_decode(&istream, example_OneofMessage_fields, &decoded)) {
printf("Échec du décodage\n");
return;
}
printf("int_value : %u\n", (unsigned int)decoded.value.int_value);
}
void encode_decode_float_value() {
uint8_t buffer[128];
size_t message_length;
example_OneofMessage message = example_OneofMessage_init_zero;
message.which_value = example_OneofMessage_float_value_tag;
message.value.float_value = 3.14159f;
pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
if (!pb_encode(&ostream, example_OneofMessage_fields, &message)) {
printf("Échec de l'encodage\n");
return;
}
message_length = ostream.bytes_written;
example_OneofMessage decoded = example_OneofMessage_init_zero;
pb_istream_t istream = pb_istream_from_buffer(buffer, message_length);
if (!pb_decode(&istream, example_OneofMessage_fields, &decoded)) {
printf("Échec du décodage\n");
return;
}
printf("float_value : %f\n", decoded.value.float_value);
}
void encode_decode_string_value() {
uint8_t buffer[128];
size_t message_length;
example_OneofMessage message = example_OneofMessage_init_zero;
message.which_value = example_OneofMessage_string_value_tag;
strncpy(message.value.string_value, "Hello", sizeof(message.value.string_value) - 1);
pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
if (!pb_encode(&ostream, example_OneofMessage_fields, &message)) {
printf("Échec de l'encodage\n");
return;
}
message_length = ostream.bytes_written;
example_OneofMessage decoded = example_OneofMessage_init_zero;
pb_istream_t istream = pb_istream_from_buffer(buffer, message_length);
if (!pb_decode(&istream, example_OneofMessage_fields, &decoded)) {
printf("Échec du décodage\n");
return;
}
printf("string_value : %s\n", decoded.value.string_value);
}
int main() {
printf("Test de tous les types oneof :\n");
encode_decode_int_value();
encode_decode_float_value();
encode_decode_string_value();
return 0;
}Points clés
- Définition oneof : Utilisez le mot-clé
oneofdans le proto pour définir des types union - which_field généré : NanoPB génère une énumération
which_fieldnamepour suivre quel champ est défini - Encodage : Définissez
which_*avec le tag approprié, puis définissez la valeur du champ - Décodage : Vérifiez
which_*pour déterminer quel champ a été défini - Mutuellement exclusifs : Un seul champ d’un oneof peut être défini à la fois
- Sémantique d’union : Similaire aux unions C mais avec sécurité de type
- Efficacité mémoire : Seule la mémoire pour un seul champ est allouée
Quand utiliser oneof
- Lorsqu’une seule parmi plusieurs valeurs possibles doit être présente
- Lorsque vous voulez économiser de la mémoire en n’allouant pas d’espace pour tous les champs
- Lorsque vous avez besoin d’unions de type sûr
- Lors de la représentation de types variants ou d’unions étiquetées
- Lors de l’implémentation de messages polymorphes
Sortie attendue
Encoded 2 bytes (int_value)
Encoded data: 08 2a
Decoded value:
int_value: 42Sortie attendue (tous les types)
Testing all oneof types:
int_value: 42
float_value: 3.141590
string_value: HelloDifférences par rapport au C++
La version C est presque identique à la version C++ :
- 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 oneof
- 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++
- Messages imbriqués en C
- Types oneof/union en C++
- Convertisseurs de tableaux personnalisés en C++
- Convertisseurs de tableaux personnalisés en C