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
Erstellen Sie 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
Generieren Sie den NanoPB-Code mit einer .options-Datei, um String-Puffergrößen festzulegen:
Erstellen Sie 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() {
// Buffer for encoded message
uint8_t buffer[128];
size_t message_length;
// --- ENCODING (int_value) ---
example_OneofMessage message = example_OneofMessage_init_zero;
// Set oneof field (only one can be set at a time)
message.which_value = example_OneofMessage_int_value_tag;
message.value.int_value = 42;
// Create stream for encoding
pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
// Encode the message
if (!pb_encode(&ostream, example_OneofMessage_fields, &message)) {
printf("Encoding failed: %s\n", PB_GET_ERROR(&ostream));
return 1;
}
message_length = ostream.bytes_written;
printf("Encoded %zu bytes (int_value)\n", message_length);
// Print hex dump
printf("Encoded data: ");
for (size_t i = 0; i < message_length; i++) {
printf("%02x ", buffer[i]);
}
printf("\n");
// --- DECODING ---
example_OneofMessage decoded = example_OneofMessage_init_zero;
// Create stream for decoding
pb_istream_t istream = pb_istream_from_buffer(buffer, message_length);
// Decode the message
if (!pb_decode(&istream, example_OneofMessage_fields, &decoded)) {
printf("Decoding failed: %s\n", PB_GET_ERROR(&istream));
return 1;
}
// Print decoded value
printf("Decoded value:\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(" (none)\n");
break;
}
return 0;
}Kompilierbefehl
Kompilieren Sie das Beispiel mit NanoPB. NanoPB wird typischerweise verwendet, indem die Quelldateien direkt in Ihr 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 Ihrem Projekt kompiliert werden. Sie können diese aus dem NanoPB-GitHub-Repository beziehen.
Python-Testskript
Um die Kodierung zu überprüfen, können Sie die Python-Protobuf-Bibliothek verwenden:
import oneof_pb2
# Read the binary data
with open('encoded.bin', 'rb') as f:
data = f.read()
# Decode
msg = oneof_pb2.OneofMessage()
msg.ParseFromString(data)
print("Python decoded values:")
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}")Kompilieren Sie zunächst die Python-Protobuf-Definitionen:
protoc --python_out=. oneof.protoÄndern Sie dann das C-Beispiel so, dass die kodierten Daten in einer Datei gespeichert werden:
// After encoding, add this:
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("Encoding failed\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("Decoding failed\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("Encoding failed\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("Decoding failed\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("Encoding failed\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("Decoding failed\n");
return;
}
printf("string_value: %s\n", decoded.value.string_value);
}
int main() {
printf("Testing all oneof types:\n");
encode_decode_int_value();
encode_decode_float_value();
encode_decode_string_value();
return 0;
}Wichtige Punkte
- oneof-Definition: Verwenden Sie 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: Setzen Sie
which_*auf das entsprechende Tag und setzen Sie dann den Feldwert - Dekodierung: Prüfen Sie
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 Sie Speicher sparen möchten, indem Sie nicht für alle Felder Platz reservieren
- Wenn Sie typsichere Unions benötigen
- Bei der Darstellung von Variant-Typen oder getaggten Unions
- Bei der Implementierung polymorpher Nachrichten
Erwartete Ausgabe
Encoded 2 bytes (int_value)
Encoded data: 08 2a
Decoded value:
int_value: 42Erwartete Ausgabe (alle Typen)
Testing all oneof types:
int_value: 42
float_value: 3.141590
string_value: HelloUnterschiede zu C++
Die C-Version ist nahezu identisch mit der C++-Version:
- Verwenden Sie 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