NanoPB: C'de ofset ve boyutlu özel dizi dönüştürücüler

Ayrıca bakınız: C++ sürümü: C++‘da özel dizi dönüştürücüler 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 özel dizi dönüştürücülerin nasıl işleneceğini gösterir.

Proto tanımı

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

custom_array.proto
syntax = "proto3";

package example;

message CustomArrayMessage {
  repeated uint32 values = 1;
}

NanoPB kodu oluşturma

Maksimum sayıyı belirtmek için bir .options dosyası ile NanoPB kodunu oluşturun:

custom_array.options oluşturun:

custom_array.options
example.CustomArrayMessage.values max_count:20

Ardından oluşturun:

generate_nanopb_custom_array.sh
protoc --nanopb_out=. custom_array.proto

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

Özel dizi dönüştürücülü C örneği

Ofset ve boyutla özel bir dizi dönüştürücü uygulayan tam bir C örneği:

custom_array_example.c
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <limits.h>
#include "custom_array.pb.h"
#include "pb_encode.h"
#include "pb_decode.h"

// Özel dizi dönüştürücü bağlamı
typedef struct {
    const uint32_t* data;
    size_t size;
    size_t offset;
    size_t count;
} array_context_t;

// Ofset ve boyutlu uint32 dizisi için kodlama geri çağrısı
bool uint32_array_encode_callback(pb_ostream_t *stream, const pb_field_t *field, void * const *arg) {
    const array_context_t* ctx = (const array_context_t*)*arg;
    
    size_t start = ctx->offset;
    size_t end = (ctx->offset + ctx->count < ctx->size) ? 
                 ctx->offset + ctx->count : ctx->size;
    
    for (size_t i = start; i < end; i++) {
        if (!pb_encode_tag_for_field(stream, field))
            return false;
        
        if (!pb_encode_varint(stream, ctx->data[i]))
            return false;
    }
    
    return true;
}

int main() {
    // Kodlanmış mesaj için arabellek
    uint8_t buffer[256];
    size_t message_length;
    
    // 10 elemanlı dizi oluştur
    uint32_t values[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    // --- KODLAMA ---
    example_CustomArrayMessage message = example_CustomArrayMessage_init_zero;
    
    // Bağlamı ayarla: 3-7. elemanları kodla (offset=2, count=5)
    array_context_t ctx = {
        .data = values,
        .size = 10,
        .offset = 2,
        .count = 5
    };
    
    // Geri çağrıyı ayarla
    message.values.funcs.encode = uint32_array_encode_callback;
    message.values.arg = &ctx;
    
    // Kodlama için akış oluştur
    pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
    
    // Mesajı kodla
    if (!pb_encode(&ostream, example_CustomArrayMessage_fields, &message)) {
        printf("Encoding failed: %s\n", PB_GET_ERROR(&ostream));
        return 1;
    }
    
    message_length = ostream.bytes_written;
    printf("Encoded %zu bytes (elements 3-7)\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");
    
    // Kodlanan değerleri yazdır
    printf("Encoded values: ");
    for (size_t i = 2; i < 2 + 5 && i < 10; i++) {
        printf("%u ", values[i]);
    }
    printf("\n");
    
    return 0;
}

Derleme komutu

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

compile_custom_array_example.sh
gcc -o custom_array_example custom_array_example.c custom_array.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.

Önemli noktalar

  • Bağlam yapısı: Ofset, sayı ve veri işaretçisini geçirmek için bir struct kullanın
  • Ofset mantığı: Belirtilen ofsetten kodlamaya başlayın
  • Sayı sınırı: Ofsetten sonra en fazla count elemanı kodlayın
  • Geri çağrı deseni: Özel kodlama fonksiyonuyla pb_callback_t kullanın
  • Esneklik: Kopyalamadan bir dizinin herhangi bir dilimini kodlayabilir
  • Bellek verimliliği: Geçici diziler oluşturmaya gerek yok
  • KKS-Firmware deseni: KKS-Firmware’den ArrayConverterWithOffsetAndSize’a dayanır

Özel dizi dönüştürücüler ne zaman kullanılır

  • Büyük bir dizinin alt kümesini kodlamanız gerektiğinde
  • Veri kopyalamaktan kaçınmak istediğinizde
  • Sayfalama veya parçalama uygularken
  • Dizi düzeni protobuf gereksinimleriyle eşleşmediğinde
  • Diziler için özel kodlama mantığına ihtiyaç duyduğunuzda

Beklenen çıktı

custom_array_expected_output.txt
Encoded 10 bytes (elements 3-7)
Encoded data: 08 03 08 04 08 05 08 06 08 07 
Encoded values: 3 4 5 6 7 

Yalnızca 3, 4, 5, 6, 7 numaralı elemanların kodlandığına dikkat edin (ofset 2’den başlayan 5 eleman).

Gelişmiş: Genel şablon benzeri uygulama

Daha genel bir C uygulaması (şablonları taklit ederek):

custom_array_generic.c
#include <stddef.h>
#include <limits.h>

#define MAX_OFFSET_SIZE (SIZE_MAX / 2)

typedef struct {
    const void* data;
    size_t element_size;
    size_t size;
    size_t offset;
    size_t count;
    bool (*encode_element)(pb_ostream_t*, const pb_field_t*, const void*);
} generic_array_context_t;

bool generic_array_encode_callback(pb_ostream_t *stream, const pb_field_t *field, void * const *arg) {
    const generic_array_context_t* ctx = (const generic_array_context_t*)*arg;
    
    size_t start = ctx->offset;
    size_t end = (ctx->offset + ctx->count < ctx->size) ? 
                 ctx->offset + ctx->count : ctx->size;
    
    const uint8_t* data = (const uint8_t*)ctx->data;
    
    for (size_t i = start; i < end; i++) {
        const void* element = data + (i * ctx->element_size);
        if (!ctx->encode_element(stream, field, element)) {
            return false;
        }
    }
    
    return true;
}

Gerçek dünya kullanımı

Bu desen KKS-Firmware’de şunlar için kullanılır:

  • Daha büyük bir kanal dizisinden yalnızca aktif kanalları kodlama
  • Bant genişliğinden tasarruf etmek için kısmi sensör verileri gönderme
  • Farklı güncellemeler uygulama
  • Seyrek dizileri verimli şekilde işleme

C++‘dan farkları

C sürümü C++‘dan birkaç yönden farklıdır:

  • Şablon yok: Bunun yerine struct’lar ve fonksiyon işaretçileri kullanın
  • Manuel bağlam yönetimi: Bağlamı açıkça geçirmeniz gerekir
  • std::array yok: Boyut takibiyle düz C dizileri kullanın
  • std::algorithm yok: Yinelemeyi manuel olarak uygulayın
  • Aynı geri çağrı deseni: İkisi de pb_callback_t kullanır

Diğer NanoPB yazıları


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