Создание обратных вызовов и структур для повторного поля в сообщении protobuf в nanopb в c

У меня есть протокольное сообщение, определенное как:

message SimpleMessage {
repeated int32 number = 1;}

теперь, после компиляции, поле имеет pb_callback_t и я предполагаю написать эту функцию. (без файла.options)

теперь, где и что должна содержать функция? где хранятся сами данные и как я могу получить к ним доступ / назначить новые данные?

* РЕДАКТИРОВАТЬ *

согласно ответу @Groo, это код, который я пробовал:

typedef struct {
    int numbers_decoded;
} DecodingState;

bool read_single_number(pb_istream_t *istream, const pb_field_t *field, void **arg)
{
    // get the pointer to the custom state
    DecodingState *state = (DecodingState*)(*arg);

    int32_t value;
    if (!pb_decode_varint32(istream, &value))
    {
        const char * error = PB_GET_ERROR(istream);
        printf("Protobuf error: %s", error);
        return false;
    }

    printf("Decoded successfully: %d", value);
    state->numbers_decoded++;

    return true;
}

int main(void) {
    int32_t arr[3] = {10, 22, 342};
    uint8_t buffer[128];
    size_t message_length;
    bool status;
    SimpleMessage simple = SimpleMessage_init_zero;

    printf("\nbefore : arr[0] = %d\n",arr[0]);

    // set the argument and the callback fn
    simple.number.arg = &arr;
    simple.number.funcs.decode = read_single_number;

    pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
    status = pb_encode(&ostream, SimpleMessage_fields, &simple);

    message_length = ostream.bytes_written;
    SimpleMessage simple1 = SimpleMessage_init_zero;
    simple = simple1;
    arr[0] = 0;
    pb_istream_t istream = pb_istream_from_buffer(buffer, message_length);
    // this function will call read_single_number several times
    status = pb_decode(&istream, SimpleMessage_fields, &simple);
    printf("\nafter : arr[0] = %d\n",arr[0]);

    return EXIT_SUCCESS;
}

и вывод:

перед: обр [0] = 10

Декодировано успешно: 17

после: arr[0] = 0

что я делаю не так?

2 ответа

Решение

Вы можете использовать некоторые прототипные флаги для Nanopb, чтобы заставить Nanopb генерировать структуры со статически размещенными массивами.

Однако стандартное поведение протогена nanopb заключается в создании функции обратного вызова, которая вызывается nanopb во время кодирования (один раз для всего списка) и декодирования (один раз для каждого элемента в списке). Это иногда предпочитается во встроенных системах с малым объемом памяти, поскольку вам не нужно выделять более одного элемента за раз.

Итак, для вашего .proto файл:

message SimpleMessage {
    repeated int32 number = 1;
}

Вы можете получить что-то вроде:

typedef struct _SimpleMessage {
    pb_callback_t number;
} SimpleMessage;

Это означает, что вам придется создать собственную функцию обратного вызова, которая будет вызываться для каждого элемента подряд.

Итак, для простоты, скажем, у вас есть простой список "переменной длины", подобный этому:

#define MAX_NUMBERS 32

typedef struct
{
    int32_t numbers[MAX_NUMBERS];
    int32_t numbers_count;
}
IntList;

// add a number to the int list
void IntList_add_number(IntList * list, int32_t number)
{
    if (list->numbers_count < MAX_NUMBERS)
    {
        list->numbers[list->numbers_count] = number;
        list->numbers_count++;
    }
}

Очевидно, что для такого примера использование обратных вызовов не имело бы никакого смысла, но это делает пример простым.

Кодировка обратного вызова должна выполнять итерацию по списку и записывать тег protobuf и значение для каждого элемента в списке:

bool SimpleMessage_encode_numbers(pb_ostream_t *ostream, const pb_field_t *field, void * const *arg)
{
    IntList * source = (IntList*)(*arg);

    // encode all numbers
    for (int i = 0; i < source->numbers_count; i++)
    {
        if (!pb_encode_tag_for_field(ostream, field))
        {
            const char * error = PB_GET_ERROR(ostream);
            printf("SimpleMessage_encode_numbers error: %s", error);
            return false;
        }

        if (!pb_encode_svarint(ostream, source->numbers[i]))
        {
            const char * error = PB_GET_ERROR(ostream);
            printf("SimpleMessage_encode_numbers error: %s", error);
            return false;
        }
    }

    return true;
}

Декодирование обратного вызова вызывается один раз для каждого элемента и "добавляется" в список:

bool SimpleMessage_decode_single_number(pb_istream_t *istream, const pb_field_t *field, void **arg)
{
    IntList * dest = (IntList*)(*arg);

    // decode single number
    int64_t number;
    if (!pb_decode_svarint(istream, &number))
    {
        const char * error = PB_GET_ERROR(istream);
        printf("SimpleMessage_decode_single_number error: %s", error);
        return false;
    }

    // add to destination list
    IntList_add_number(dest, (int32_t)number);
    return true;
}

С этими двумя на месте, вы должны быть осторожны, чтобы назначить правильный обратный вызов для правильной функции:

uint8_t buffer[128];
size_t total_bytes_encoded = 0;

// encoding
{
    // prepare the actual "variable" array
    IntList actualData = { 0 };
    IntList_add_number(&actualData, 123);
    IntList_add_number(&actualData, 456);
    IntList_add_number(&actualData, 789);

    // prepare the nanopb ENCODING callback
    SimpleMessage msg = SimpleMessage_init_zero;
    msg.number.arg = &actualData;
    msg.number.funcs.encode = SimpleMessage_encode_numbers;

    // call nanopb
    pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
    if (!pb_encode(&ostream, SimpleMessage_fields, &msg))
    {
        const char * error = PB_GET_ERROR(&ostream);
        printf("pb_encode error: %s", error);
        return;
    }

    total_bytes_encoded = ostream.bytes_written;
    printf("Encoded size: %d", total_bytes_encoded);
}

И похоже для расшифровки:

// decoding
{
    // empty array for decoding
    IntList decodedData = { 0 };

    // prepare the nanopb DECODING callback
    SimpleMessage msg = SimpleMessage_init_zero;
    msg.number.arg = &decodedData;
    msg.number.funcs.decode = SimpleMessage_decode_single_number;

    // call nanopb
    pb_istream_t istream = pb_istream_from_buffer(buffer, total_bytes_encoded);
    if (!pb_decode(&istream, SimpleMessage_fields, &msg))
    {
        const char * error = PB_GET_ERROR(&istream);
        printf("pb_decode error: %s", error);
        return;
    }

    printf("Bytes decoded: %d", total_bytes_encoded - istream.bytes_left);
}

Если внутри вашего сообщения есть повторяющаяся структура, ваш обратный вызов не будет использовать примитивные функции nanopb (например, pb_decode_varint32 выше), но опять pb_decode для каждого конкретного типа сообщения. Ваш обратный вызов также может прикрепить новые обратные вызовы к этим вложенным структурам, если это необходимо.

Чтобы дополнить ответ Груо, вот ответы на ваши конкретные вопросы.

1. Теперь, где и что должна содержать функция?

Groo предоставил хорошее объяснение функций обратного вызова. network_server Пример в репозитории nanopb также использует обратные вызовы и может быть полезной ссылкой: network_server / server.c network_server / client.c

2. Где хранятся сами данные?

Где угодно! Весь смысл обратных вызовов nanopb заключается в том, что он дает вам полную гибкость в принятии решения о том, как хранить ваши данные. В некоторых случаях вы можете даже обрабатывать данные на лету, нигде не сохраняя их.

Например, network_server Приведенный выше пример получает имена файлов из файловой системы и отправляет их в сеть напрямую - таким образом он может обрабатывать любое количество файлов, не требуя большого объема памяти.

3. Как я могу получить к нему доступ / присвоить ему новые данные?

Теперь это обратная сторона обратных вызовов - вам придется реализовать свои собственные функции доступа и распределения для любого хранилища, которое вы используете. Вот почему для наиболее распространенных случаев: статическое распределение (с фиксированным максимальным размером) или динамическое распределение (которое malloc()Требуемый объем памяти) удобнее.

Другие вопросы по тегам