NanoPB : Comment gérer les types chaîne en C
Voir aussi : Version C++ : Comment gérer les types chaîne 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 types chaîne en C avec NanoPB.
Définition Proto
Créez d’abord un fichier .proto avec des champs de type chaîne :
syntax = "proto3";
package example;
message StringMessage {
string name = 1;
string description = 2;
}Génération du code NanoPB
Générez le code NanoPB avec un fichier .options pour spécifier les tailles des tampons de chaîne :
Créez strings.options :
example.StringMessage.name max_size:64
example.StringMessage.description max_size:256Générez ensuite :
protoc --nanopb_out=. strings.protoCela générera strings.pb.h et strings.pb.c.
Exemple C avec tampons de taille fixe
Voici un exemple C complet utilisant des tampons de taille fixe :
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "strings.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_StringMessage message = example_StringMessage_init_zero;
// Définir les valeurs des chaînes (doivent tenir dans la taille du tampon)
const char* name = "NanoPB";
const char* description = "Protocol Buffers for embedded systems";
strncpy(message.name, name, sizeof(message.name) - 1);
strncpy(message.description, description, sizeof(message.description) - 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_StringMessage_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_StringMessage decoded = example_StringMessage_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_StringMessage_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(" description : %s\n", decoded.description);
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 :
gcc -o strings_example strings_example.c strings.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 strings_pb2
# Lire les données binaires
with open('encoded.bin', 'rb') as f:
data = f.read()
# Décoder
msg = strings_pb2.StringMessage()
msg.ParseFromString(data)
print("Valeurs décodées par Python :")
print(f" name : {msg.name}")
print(f" description : {msg.description}")Compilez d’abord les définitions protobuf Python :
protoc --python_out=. strings.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 : Chaînes basées sur des callbacks
Pour une gestion dynamique des chaînes, vous pouvez utiliser des callbacks. Créez strings_callback.options :
# Utiliser un callback pour les chaînes dynamiques
msg.StringMessage.name callback
msg.StringMessage.description callbackRégénérez ensuite et utilisez cette approche :
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "strings.pb.h"
#include "pb_encode.h"
#include "pb_decode.h"
typedef struct {
char* data;
size_t size;
} dynamic_string_t;
// Callback d'encodage pour les chaînes
bool string_encode_callback(pb_ostream_t *stream, const pb_field_t *field, void * const *arg) {
const dynamic_string_t* str = (const dynamic_string_t*)*arg;
if (!pb_encode_tag_for_field(stream, field))
return false;
return pb_encode_string(stream, (const pb_byte_t*)str->data, str->size);
}
// Callback de décodage pour les chaînes
bool string_decode_callback(pb_istream_t *stream, const pb_field_t *field, void **arg) {
dynamic_string_t* str = (dynamic_string_t*)*arg;
// Allouer le tampon
size_t size = stream->bytes_left;
str->data = (char*)malloc(size + 1);
if (!pb_read(stream, (pb_byte_t*)str->data, size)) {
free(str->data);
str->data = NULL;
return false;
}
str->data[size] = '\0'; // Terminaison nulle
str->size = size;
return true;
}
int main() {
uint8_t buffer[256];
size_t message_length;
dynamic_string_t name = {"NanoPB", 6};
dynamic_string_t description = {"Protocol Buffers for embedded systems", 38};
// --- ENCODAGE ---
example_StringMessage message = example_StringMessage_init_zero;
message.name.funcs.encode = string_encode_callback;
message.name.arg = &name;
message.description.funcs.encode = string_encode_callback;
message.description.arg = &description;
pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
if (!pb_encode(&ostream, example_StringMessage_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_StringMessage decoded = example_StringMessage_init_zero;
dynamic_string_t decoded_name = {NULL, 0};
dynamic_string_t decoded_description = {NULL, 0};
decoded.name.funcs.decode = string_decode_callback;
decoded.name.arg = &decoded_name;
decoded.description.funcs.decode = string_decode_callback;
decoded.description.arg = &decoded_description;
pb_istream_t istream = pb_istream_from_buffer(buffer, message_length);
if (!pb_decode(&istream, example_StringMessage_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.data);
printf(" description : %s\n", decoded_description.data);
// Libérer la mémoire allouée
free(decoded_name.data);
free(decoded_description.data);
return 0;
}Points clés
- Tampons de taille fixe : Utilisez
max_sizedans le fichier .options pour une allocation statique simple - Basé sur des callbacks : Utilisez
callbackdans le fichier .options pour une gestion dynamique des chaînes - Taille fixe : Utilisez
strncpypour copier les chaînes, assurez-vous de la terminaison nulle - Basé sur des callbacks : Implémentez des callbacks d’encodage/décodage avec gestion manuelle de la mémoire
- Vérifiez toujours les tailles de tampon pour éviter les dépassements
- N’oubliez pas de libérer la mémoire allouée dans l’approche basée sur des callbacks
- Le C nécessite une gestion manuelle de la mémoire (malloc/free) pour les chaînes dynamiques
Quand utiliser quelle approche
- Tampons de taille fixe : Quand vous connaissez les tailles maximales des chaînes et que vous voulez un code simple
- Basé sur des callbacks : Quand les tailles des chaînes sont variables ou que vous avez besoin d’une allocation dynamique de mémoire
Sortie attendue
Encoded 38 bytes
Encoded data: 0a 06 4e 61 6e 6f 50 42 12 1e 50 72 6f 74 6f 63 6f 6c 20 42 75 66 66 65 72 73 20 66 6f 72 20 65 6d 62 65 64 64 65 64 20 73 79 73 74 65 6d 73
Decoded values:
name: NanoPB
description: Protocol Buffers for embedded systemsDifférences par rapport au C++
La version C est presque identique à la version C++, avec des différences clés :
- Utilisez
malloc/freeau lieu denew/deletepour la mémoire dynamique - Gestion manuelle de la mémoire requise (pas de destructeurs)
- Utilisez une structure pour l’enveloppe de chaîne dynamique au lieu de std::string
- N’oubliez pas de terminer manuellement les chaînes par un caractère nul lorsque c’est nécessaire
Plus de posts sur NanoPB
- Types scalaires de base en C++
- Types scalaires de base 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 tableaux personnalisés en C++
- Convertisseurs de tableaux personnalisés en C