NanoPB: C'de oneof (union) tipleri nasıl işlenir

Ayrıca bakınız: C++ sürümü: C++‘da oneof/union tipleri nasıl işlenir

NanoPB, gömülü sistemler için kod boyutu açısından optimize edilmiş bir Protocol Buffers uygulamasıdır. Bu yazı, NanoPB ile C’de oneof (union) tiplerinin nasıl işleneceğini gösterir.

Proto tanımı

Önce oneof alanları içeren bir .proto dosyası oluşturun:

oneof.proto
syntax = "proto3";

package example;

message OneofMessage {
  oneof value {
    uint32 int_value = 1;
    float float_value = 2;
    string string_value = 3;
  }
}

NanoPB kodu oluşturma

String arabellek boyutlarını belirtmek için bir .options dosyası ile NanoPB kodunu oluşturun:

oneof.options oluşturun:

oneof.options
example.OneofMessage.string_value max_size:32

Ardından oluşturun:

generate_nanopb_oneof.sh
protoc --nanopb_out=. oneof.proto

Bu, oneof.pb.h ve oneof.pb.c dosyalarını oluşturur.

C örneği

Oneof alanlarını işleyen tam bir C örneği:

oneof_example.c
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "oneof.pb.h"
#include "pb_encode.h"
#include "pb_decode.h"

int main() {
    // Kodlanmış mesaj için arabellek
    uint8_t buffer[128];
    size_t message_length;
    
    // --- KODLAMA (int_value) ---
    example_OneofMessage message = example_OneofMessage_init_zero;
    
    // Oneof alanını ayarla (aynı anda yalnızca biri ayarlanabilir)
    message.which_value = example_OneofMessage_int_value_tag;
    message.value.int_value = 42;
    
    // Kodlama için akış oluştur
    pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
    
    // Mesajı kodla
    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);
    
    // Hex dökümünü yazdır
    printf("Encoded data: ");
    for (size_t i = 0; i < message_length; i++) {
        printf("%02x ", buffer[i]);
    }
    printf("\n");
    
    // --- KOD ÇÖZME ---
    example_OneofMessage decoded = example_OneofMessage_init_zero;
    
    // Kod çözme için akış oluştur
    pb_istream_t istream = pb_istream_from_buffer(buffer, message_length);
    
    // Mesajın kodunu çöz
    if (!pb_decode(&istream, example_OneofMessage_fields, &decoded)) {
        printf("Decoding failed: %s\n", PB_GET_ERROR(&istream));
        return 1;
    }
    
    // Kodu çözülen değeri yazdır
    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;
}

Derleme komutu

Örneği nanopb ile derleyin. NanoPB genellikle kaynak dosyaları doğrudan projenize dahil edilerek kullanılır:

compile_oneof_example.sh
gcc -o oneof_example oneof_example.c oneof.pb.c pb_common.c pb_encode.c pb_decode.c -I.

Not: NanoPB kaynak dosyaları (pb_common.c, pb_encode.c, pb_decode.c) projenizle birlikte doğrudan derlenmelidir. Bunları NanoPB GitHub deposundan edinebilirsiniz.

Python test betiği

Kodlamayı doğrulamak için Python’un protobuf kütüphanesini kullanabilirsiniz:

test_oneof.py
import oneof_pb2

# İkili veriyi oku
with open('encoded.bin', 'rb') as f:
    data = f.read()

# Kod çöz
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}")

Önce Python protobuf tanımlarını derleyin:

compile_python_oneof.sh
protoc --python_out=. oneof.proto

Ardından C örneğini, kodlanmış veriyi bir dosyaya kaydedecek şekilde değiştirin:

save_encoded_oneof.c
// Kodlamadan sonra bunu ekleyin:
FILE *f = fopen("encoded.bin", "wb");
fwrite(buffer, 1, message_length, f);
fclose(f);

Tüm oneof tipleriyle örnek

Tüm oneof tiplerini gösteren bir örnek:

oneof_all_example.c
#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;
}

Önemli noktalar

  • Oneof tanımı: Union tiplerini tanımlamak için proto’da oneof anahtar kelimesini kullanın
  • Oluşturulan which_field: NanoPB, hangi alanın ayarlandığını izlemek için which_fieldname enum’u oluşturur
  • Kodlama: which_* değerini uygun etikete ayarlayın, ardından alan değerini ayarlayın
  • Kod çözme: Hangi alanın ayarlandığını belirlemek için which_* değerini kontrol edin
  • Karşılıklı dışlama: Bir oneof’ta aynı anda yalnızca bir alan ayarlanabilir
  • Union semantiği: C union’larına benzer ancak tip güvenlidir
  • Bellek verimliliği: Yalnızca bir alan için bellek ayrılır

Oneof ne zaman kullanılır

  • Birkaç olası değerden yalnızca birinin bulunması gerektiğinde
  • Tüm alanlar için yer ayırmayarak bellekten tasarruf etmek istediğinizde
  • Tip güvenli union’lara ihtiyaç duyduğunuzda
  • Variant tipleri veya etiketli union’ları temsil ederken
  • Polimorfik mesajlar uygularken

Beklenen çıktı

oneof_expected_output.txt
Encoded 2 bytes (int_value)
Encoded data: 08 2a 
Decoded value:
  int_value: 42

Beklenen çıktı (tüm tipler)

oneof_all_expected_output.txt
Testing all oneof types:
int_value: 42
float_value: 3.141590
string_value: Hello

C++‘dan farkları

C sürümü, C++ sürümüyle neredeyse aynıdır:

  • C’de printf format belirteçleri için açık cast kullanın
  • Oneof işleme için C++‘a özgü özellikler gerekmez
  • Oluşturulan kod her iki dil için de aynıdır
  • Bellek yönetimi aynıdır (her iki durumda da statik tahsis)

Diğer NanoPB yazıları


Check out similar posts by category: Gömülü C/C++ Protocol Buffers