Veja também: Versão C++: Como lidar com conversores de array personalizados em C++
NanoPB é uma implementação de Protocol Buffers otimizada para tamanho de código, destinada a sistemas embarcados. Este post mostra como lidar com conversores de array personalizados em C com NanoPB.
Definição Proto
Primeiro, crie um arquivo .proto com campos repeated (repetidos):
syntax = "proto3";
package example;
message CustomArrayMessage {
repeated uint32 values = 1;
}Gerar código NanoPB
Gere o código NanoPB com um arquivo .options para especificar a contagem máxima:
Crie custom_array.options:
example.CustomArrayMessage.values max_count:20Depois gere:
protoc --nanopb_out=. custom_array.protoIsso irá gerar custom_array.pb.h e custom_array.pb.c.
Exemplo em C com conversor de array personalizado
Aqui está um exemplo completo em C implementando um conversor de array personalizado com offset e tamanho:
#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"
// Contexto do conversor de array personalizado
typedef struct {
const uint32_t* data;
size_t size;
size_t offset;
size_t count;
} array_context_t;
// Callback de codificação para array uint32 com offset e tamanho
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() {
// Buffer para mensagem codificada
uint8_t buffer[256];
size_t message_length;
// Criar array com 10 elementos
uint32_t values[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// --- CODIFICAÇÃO ---
example_CustomArrayMessage message = example_CustomArrayMessage_init_zero;
// Configurar contexto: codificar elementos 3-7 (offset=2, count=5)
array_context_t ctx = {
.data = values,
.size = 10,
.offset = 2,
.count = 5
};
// Configurar callback
message.values.funcs.encode = uint32_array_encode_callback;
message.values.arg = &ctx;
// Criar stream para codificação
pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
// Codificar a mensagem
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);
// Imprimir hex dump
printf("Encoded data: ");
for (size_t i = 0; i < message_length; i++) {
printf("%02x ", buffer[i]);
}
printf("\n");
// Imprimir o que foi codificado
printf("Encoded values: ");
for (size_t i = 2; i < 2 + 5 && i < 10; i++) {
printf("%u ", values[i]);
}
printf("\n");
return 0;
}Comando de compilação
Compile o exemplo com nanopb. NanoPB é tipicamente usado incluindo os arquivos fonte diretamente no seu projeto:
gcc -o custom_array_example custom_array_example.c custom_array.pb.c pb_common.c pb_encode.c pb_decode.c -I.Nota: Os arquivos fonte do NanoPB (pb_common.c, pb_encode.c, pb_decode.c) precisam ser compilados diretamente com seu projeto. Você pode obtê-los no repositório GitHub do NanoPB.
Pontos principais
- Estrutura de contexto: Use uma struct para passar offset, count e ponteiro de dados
- Lógica de offset: Comece a codificação no offset especificado
- Limite de contagem: Codifique no máximo count elementos após o offset
- Padrão de callback: Use pb_callback_t com função de codificação personalizada
- Flexibilidade: Pode codificar qualquer fatia de um array sem copiar
- Eficiência de memória: Não há necessidade de criar arrays temporários
- Padrão KKS-Firmware: Baseado em ArrayConverterWithOffsetAndSize do KKS-Firmware
Quando usar conversores de array personalizados
- Quando você precisa codificar um subconjunto de um array grande
- Quando você quer evitar copiar dados
- Ao implementar paginação ou fragmentação (chunking)
- Quando o layout do array não corresponde aos requisitos do protobuf
- Quando você precisa de lógica de codificação personalizada para arrays
Saída esperada
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 Note que apenas os elementos 3, 4, 5, 6, 7 são codificados (5 elementos começando no offset 2).
Avançado: Implementação genérica similar a templates
Para uma implementação em C mais genérica (simulando templates):
#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;
}Uso no mundo real
Este padrão é usado no KKS-Firmware para:
- Codificar apenas canais ativos de um array de canais maior
- Enviar dados parciais de sensores para economizar banda
- Implementar atualizações diferenciais
- Lidar com arrays esparsos de forma eficiente
Diferenças do C++
A versão em C difere do C++ de várias maneiras:
- Sem templates: Use structs e ponteiros de função em vez disso
- Gerenciamento manual de contexto: Deve passar o contexto explicitamente
- Sem std::array: Use arrays C simples com rastreamento de tamanho
- Sem std::algorithm: Implemente a iteração manualmente
- Mesmo padrão de callback: Ambos usam pb_callback_t
Mais posts sobre NanoPB
- Tipos escalares básicos em C++
- Tipos escalares básicos em C
- Tipos de string em C++
- Tipos de string em C
- Tipos de bytes em C++
- Tipos de bytes em C
- Campos opcionais em C++
- Campos opcionais em C
- Campos repeated/arrays em C++
- Campos repeated/arrays em C
- Enums em C++
- Enums em C
- Mensagens aninhadas em C++
- Mensagens aninhadas em C
- Tipos oneof/union em C++
- Tipos oneof/union em C
- Conversores de array personalizados em C++