Див. також: C++ version: How to handle oneof/union types in C++
NanoPB — це оптимізована за розміром коду реалізація Protocol Buffers для вбудованих систем. Ця публікація показує, як обробляти oneof (union) типи у C за допомогою NanoPB.
Визначення Proto
Спочатку створіть файл .proto з полями oneof:
syntax = "proto3";
package example;
message OneofMessage {
oneof value {
uint32 int_value = 1;
float float_value = 2;
string string_value = 3;
}
}Генерація коду NanoPB
Згенеруйте код NanoPB з файлом .options, щоб вказати розміри строкових буферів:
Створіть oneof.options:
example.OneofMessage.string_value max_size:32Потім згенеруйте:
protoc --nanopb_out=. oneof.protoЦе згенерує oneof.pb.h та oneof.pb.c.
Приклад на C
Ось повний приклад на C для обробки полів oneof:
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "oneof.pb.h"
#include "pb_encode.h"
#include "pb_decode.h"
int main() {
// Буфер для закодованого повідомлення
uint8_t buffer[128];
size_t message_length;
// --- КОДУВАННЯ (int_value) ---
example_OneofMessage message = example_OneofMessage_init_zero;
// Встановлення поля oneof (одночасно може бути встановлено лише одне)
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("Помилка кодування: %s\n", PB_GET_ERROR(&ostream));
return 1;
}
message_length = ostream.bytes_written;
printf("Закодовано %zu байт (int_value)\n", message_length);
// Друк шістнадцяткового дампу
printf("Закодовані дані: ");
for (size_t i = 0; i < message_length; i++) {
printf("%02x ", buffer[i]);
}
printf("\n");
// --- ДЕКОДУВАННЯ ---
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("Помилка декодування: %s\n", PB_GET_ERROR(&istream));
return 1;
}
// Друк декодованого значення
printf("Декодоване значення:\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(" (немає)\n");
break;
}
return 0;
}Команда компіляції
Скомпілюйте приклад з nanopb. NanoPB зазвичай використовується шляхом безпосереднього включення вихідних файлів у ваш проєкт:
gcc -o oneof_example oneof_example.c oneof.pb.c pb_common.c pb_encode.c pb_decode.c -I.Примітка: Вихідні файли NanoPB (pb_common.c, pb_encode.c, pb_decode.c) потрібно компілювати безпосередньо з вашим проєктом. Їх можна отримати з репозиторію NanoPB на GitHub.
Скрипт тестування на Python
Щоб перевірити кодування, можна використати бібліотеку protobuf для Python:
import oneof_pb2
# Читання бінарних даних
with open('encoded.bin', 'rb') as f:
data = f.read()
# Декодування
msg = oneof_pb2.OneofMessage()
msg.ParseFromString(data)
print("Декодовані Python значення:")
print(f" Який 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}")Спочатку скомпілюйте визначення protobuf для Python:
protoc --python_out=. oneof.protoПотім змініть приклад на C, щоб зберегти закодовані дані у файл:
// Після кодування додайте це:
FILE *f = fopen("encoded.bin", "wb");
fwrite(buffer, 1, message_length, f);
fclose(f);Приклад з усіма типами oneof
Ось приклад, що демонструє всі типи 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("Помилка кодування\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("Помилка декодування\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("Помилка кодування\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("Помилка декодування\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("Помилка кодування\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("Помилка декодування\n");
return;
}
printf("string_value: %s\n", decoded.value.string_value);
}
int main() {
printf("Тестування всіх типів oneof:\n");
encode_decode_int_value();
encode_decode_float_value();
encode_decode_string_value();
return 0;
}Ключові моменти
- Визначення oneof: Використовуйте ключове слово
oneofу proto для визначення union-типів - Згенероване which_field: NanoPB генерує enum
which_fieldnameдля відстеження того, яке поле встановлено - Кодування: Встановіть
which_*у відповідний тег, потім встановіть значення поля - Декодування: Перевірте
which_*, щоб визначити, яке поле було встановлено - Взаємовиключність: Одночасно може бути встановлено лише одне поле в oneof
- Семантика union: Схожа на C-union, але з безпекою типів
- Ефективність пам’яті: Пам’ять виділяється лише для одного поля
Коли використовувати oneof
- Коли має бути присутнім лише одне з кількох можливих значень
- Коли ви хочете зекономити пам’ять, не виділяючи місце для всіх полів
- Коли потрібні union-типи з безпекою типів
- Коли представляєте варіантні типи або теговані union
- Коли реалізуєте поліморфні повідомлення
Очікуваний вивід
Закодовано 2 байт (int_value)
Закодовані дані: 08 2a
Декодоване значення:
int_value: 42Очікуваний вивід (усі типи)
Тестування всіх типів oneof:
int_value: 42
float_value: 3.141590
string_value: HelloВідмінності від C++
Версія на C майже ідентична версії на C++:
- У C використовується явне приведення типів для специфікаторів формату printf
- Для обробки oneof не потрібні специфічні для C++ можливості
- Згенерований код однаковий для обох мов
- Керування пам’яттю однакове (статичне виділення в обох випадках)
More NanoPB posts
- Basic scalar types in C++
- Basic scalar types in C
- String types in C++
- String types in C
- Bytes types in C++
- Bytes types in C
- Optional fields in C++
- Optional fields in C
- Repeated fields/arrays in C++
- Repeated fields/arrays in C
- Enums in C++
- Enums in C
- Nested messages in C++
- Nested messages in C
- Oneof/union types in C++
- Custom array converters in C++
- Custom array converters in C