NanoPB: Umgang mit oneof (Union)-Typen in C
Siehe auch: C++-Version: Umgang mit oneof/Union-Typen in C++
NanoPB ist eine auf Codegröße optimierte Protocol-Buffers-Implementierung für Embedded-Systeme. Dieser Beitrag zeigt, wie man mit oneof (Union)-Typen in C mit NanoPB umgeht.
Proto-Definition
Erstelle zunächst eine .proto-Datei mit oneof-Feldern:
syntax = "proto3";
package example;
message OneofMessage {
oneof value {
uint32 int_value = 1;
float float_value = 2;
string string_value = 3;
}
}NanoPB-Code generieren
Generiere den NanoPB-Code mit einer .options-Datei, um String-Puffergrößen festzulegen:
Erstelle oneof.options:
example.OneofMessage.string_value max_size:32Anschließend generieren:
protoc --nanopb_out=. oneof.protoDadurch werden oneof.pb.h und oneof.pb.c generiert.
C-Beispiel
Hier ist ein vollständiges C-Beispiel zur Behandlung von oneof-Feldern:
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "oneof.pb.h"
#include "pb_encode.h"
#include "pb_decode.h"
int main() {
// Puffer für kodierte Nachricht
uint8_t buffer[128];
size_t message_length;
// --- KODIERUNG (int_value) ---
example_OneofMessage message = example_OneofMessage_init_zero;
// Oneof-Feld setzen (nur eines kann gleichzeitig gesetzt werden)
message.which_value = example_OneofMessage_int_value_tag;
message.value.int_value = 42;
// Strom für Kodierung erstellen
pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
// Nachricht kodieren
if (!pb_encode(&ostream, example_OneofMessage_fields, &message)) {
printf("Kodierung fehlgeschlagen: %s\n", PB_GET_ERROR(&ostream));
return 1;
}
message_length = ostream.bytes_written;
printf("%zu Bytes kodiert (int_value)\n", message_length);
// Hex-Dump ausgeben
printf("Kodierte Daten: ");
for (size_t i = 0; i < message_length; i++) {
printf("%02x ", buffer[i]);
}
printf("\n");
// --- DEKODIERUNG ---
example_OneofMessage decoded = example_OneofMessage_init_zero;
// Strom für Dekodierung erstellen
pb_istream_t istream = pb_istream_from_buffer(buffer, message_length);
// Nachricht dekodieren
if (!pb_decode(&istream, example_OneofMessage_fields, &decoded)) {
printf("Dekodierung fehlgeschlagen: %s\n", PB_GET_ERROR(&istream));
return 1;
}
// Dekodierten Wert ausgeben
printf("Dekodierter Wert:\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(" (keiner)\n");
break;
}
return 0;
}Kompilierbefehl
Kompiliere das Beispiel mit NanoPB. NanoPB wird typischerweise verwendet, indem die Quelldateien direkt in dein Projekt eingebunden werden:
gcc -o oneof_example oneof_example.c oneof.pb.c pb_common.c pb_encode.c pb_decode.c -I.Hinweis: NanoPB-Quelldateien (pb_common.c, pb_encode.c, pb_decode.c) müssen direkt mit deinem Projekt kompiliert werden. Du kannst diese aus dem NanoPB-GitHub-Repository beziehen.
Python-Testskript
Um die Kodierung zu überprüfen, kannst du die Python-Protobuf-Bibliothek verwenden:
import oneof_pb2
# Binärdaten lesen
with open('encoded.bin', 'rb') as f:
data = f.read()
# Dekodieren
msg = oneof_pb2.OneofMessage()
msg.ParseFromString(data)
print("Python dekodierte Werte:")
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}")Kompiliere zunächst die Python-Protobuf-Definitionen:
protoc --python_out=. oneof.protoÄndere dann das C-Beispiel so, dass die kodierten Daten in einer Datei gespeichert werden:
// Nach der Kodierung, dies hinzufügen:
FILE *f = fopen("encoded.bin", "wb");
fwrite(buffer, 1, message_length, f);
fclose(f);Beispiel mit allen oneof-Typen
Hier ist ein Beispiel, das alle oneof-Typen zeigt:
#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("Kodierung fehlgeschlagen\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("Dekodierung fehlgeschlagen\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("Kodierung fehlgeschlagen\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("Dekodierung fehlgeschlagen\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("Kodierung fehlgeschlagen\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("Dekodierung fehlgeschlagen\n");
return;
}
printf("string_value: %s\n", decoded.value.string_value);
}
int main() {
printf("Teste alle oneof-Typen:\n");
encode_decode_int_value();
encode_decode_float_value();
encode_decode_string_value();
return 0;
}Wichtige Punkte
- oneof-Definition: Verwende das Schlüsselwort
oneofin Proto, um Union-Typen zu definieren - Generiertes which_field: NanoPB generiert die Enum
which_fieldname, um zu verfolgen, welches Feld gesetzt ist - Kodierung: Setze
which_*auf das entsprechende Tag und setze dann den Feldwert - Dekodierung: Prüfe
which_*, um festzustellen, welches Feld gesetzt wurde - Gegenseitig ausschließend: Es kann nur ein Feld in einem oneof gleichzeitig gesetzt sein
- Union-Semantik: Ähnlich wie C-Unions, aber mit Typsicherheit
- Speichereffizienz: Es wird nur Speicher für ein Feld reserviert
Wann man oneof verwendet
- Wenn nur einer von mehreren möglichen Werten vorhanden sein soll
- Wenn du Speicher sparen möchtest, indem du nicht für alle Felder Platz reservierst
- Wenn du typsichere Unions benötigst
- Bei der Darstellung von Variant-Typen oder getaggten Unions
- Bei der Implementierung polymorpher Nachrichten
Erwartete Ausgabe
2 Bytes kodiert (int_value)
Kodierte Daten: 08 2a
Dekodierter Wert:
int_value: 42Erwartete Ausgabe (alle Typen)
Teste alle oneof-Typen:
int_value: 42
float_value: 3.141590
string_value: HelloUnterschiede zu C++
Die C-Version ist nahezu identisch mit der C++-Version:
- Verwende explizite Typumwandlungen für printf-Formatbezeichner in C
- Für die oneof-Behandlung werden keine C++-spezifischen Funktionen benötigt
- Der generierte Code ist für beide Sprachen identisch
- Die Speicherverwaltung ist dieselbe (in beiden Fällen statische Reservierung)
Weitere NanoPB-Beiträge
- Basisskalartypen in C++
- Basisskalartypen in C
- String-Typen in C++
- String-Typen in C
- Bytes-Typen in C++
- Bytes-Typen in C
- Optionale Felder in C++
- Optionale Felder in C
- Wiederholte Felder/Arrays in C++
- Wiederholte Felder/Arrays in C
- Enums in C++
- Enums in C
- Verschachtelte Nachrichten in C++
- Verschachtelte Nachrichten in C
- Oneof/Union-Typen in C++
- Benutzerdefinierte Array-Konverter in C++
- Benutzerdefinierte Array-Konverter in C