Смотрите также: Версия для C++: Как обрабатывать типы oneof/union в 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("Encoding failed: %s\n", PB_GET_ERROR(&ostream));
return 1;
}
message_length = ostream.bytes_written;
printf("Encoded %zu bytes (int_value)\n", message_length);
// Выводим шестнадцатеричный дамп
printf("Encoded data: ");
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("Decoding failed: %s\n", PB_GET_ERROR(&istream));
return 1;
}
// Выводим декодированное значение
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;
}Команда компиляции
Скомпилируйте пример с помощью 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 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}")Сначала скомпилируйте определения 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("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;
}Ключевые моменты
- Определение oneof: Используйте ключевое слово
oneofв proto для определения типов union - Сгенерированное which_field: NanoPB генерирует перечисление
which_fieldnameдля отслеживания того, какое поле установлено - Кодирование: Установите
which_*в соответствующий тег, затем установите значение поля - Декодирование: Проверьте
which_*, чтобы определить, какое поле было установлено - Взаимоисключаемость: В oneof может быть установлено только одно поле за раз
- Семантика union: Похоже на union в C, но с типобезопасностью
- Эффективность памяти: Выделяется память только для одного поля
Когда использовать oneof
- Когда должно присутствовать только одно из нескольких возможных значений
- Когда вы хотите сэкономить память, не выделяя место для всех полей
- Когда вам нужны типобезопасные union
- При представлении вариантных типов или помеченных union
- При реализации полиморфных сообщений
Ожидаемый вывод
Encoded 2 bytes (int_value)
Encoded data: 08 2a
Decoded value:
int_value: 42Ожидаемый вывод (все типы)
Testing all oneof types:
int_value: 42
float_value: 3.141590
string_value: HelloОтличия от C++
Версия на C почти идентична версии на C++:
- Используйте явное приведение типов для спецификаторов формата printf в C
- Для обработки oneof не требуются специфичные для C++ возможности
- Сгенерированный код одинаков для обоих языков
- Управление памятью одинаково (статическое выделение в обоих случаях)
Другие посты о NanoPB
- Базовые скалярные типы в C++
- Базовые скалярные типы в C
- Строковые типы в C++
- Строковые типы в C
- Типы bytes в C++
- Типы bytes в C
- Необязательные поля в C++
- Необязательные поля в C
- Повторяющиеся поля/массивы в C++
- Повторяющиеся поля/массивы в C
- Перечисления в C++
- Перечисления в C
- Вложенные сообщения в C++
- Вложенные сообщения в C
- Типы oneof/union в C++
- Пользовательские конвертеры массивов в C++
- Пользовательские конвертеры массивов в C