NanoPB : Comment gérer les types chaîne en C

English Français

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 :

strings.proto
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 :

strings.options
example.StringMessage.name max_size:64
example.StringMessage.description max_size:256

Générez ensuite :

generate_nanopb_strings.sh
protoc --nanopb_out=. strings.proto

Cela 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 :

strings_example.c
#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 :

compile_strings_example.sh
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 :

test_strings.py
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 :

compile_python_strings.sh
protoc --python_out=. strings.proto

Modifiez ensuite l’exemple C pour enregistrer les données encodées dans un fichier :

save_encoded_strings.c
// 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 :

strings_callback.options
# Utiliser un callback pour les chaînes dynamiques
msg.StringMessage.name callback
msg.StringMessage.description callback

Régénérez ensuite et utilisez cette approche :

strings_callback_example.c
#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

Quand utiliser quelle approche

Sortie attendue

strings_expected_output.txt
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 systems

Différences par rapport au C++

La version C est presque identique à la version C++, avec des différences clés :

Plus de posts sur NanoPB


Check out similar posts by category: Embedded, C/C++, Protocol Buffers