小能豆

nanopb(protobuf)常见的3种字段手动赋值问题,求解答

javascript

主要是nanopb中,optional可选、required必选、repeated重复字段的使用中,发现每个字段都需要在代码中手动处理一些东西,比如:
1.需要手动检查required字段是否有值,必选字段未赋值也不会报错;
2.给optional字段赋值后,需要手动给存在性检查变量has_fie赋值为true;
3.repeated重复字段在赋值后,需要手动赋值令field_count变量==实际数据个数;

代码:

我使用的例子是nanopb官方的示例,simple.proto文件定义为:

syntax = "proto2";
import"nanopb.proto";
message SimpleMessage {
    required string name = 1 [(nanopb).max_size = 128];
    optional int32 number = 2 [default = 2];
    repeated int32 repeatID = 3 [(nanopb).max_count = 5];
}

编译生成的simple.pb.h中,消息的结构体定义为:

typedef struct simpleMessage{
char name[128l;
bool has_number;       // 存在性检查变量
int32_t number;
pb_size_t repeatID_count;    // 数组实际数据个数
int32_t repeatID[5];
}SimpleMessage;

程序比较简单,先序列化,再直接反序列化。main.c程序为:

#include <stdio.h>
#include <pb_encode.h>
#include <pb_decode.h>
#include "simple.pb.h"
int main()
{
    uint8_t buffer[128];
    size_t message_length;
    bool status;
    /* Encode message */
    {
        SimpleMessage message = SimpleMessage_init_zero;
        /* Create a stream that will write to our buffer. */
        pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));

        /* Fill in the message */
        /*strncpy(message.name, "Lisa", sizeof(message.name));
        message.number = 520;*/
        message.repeatID[0] = 111;
        message.repeatID[1] = 222;
        message.repeatID[2] = 333;
        printf("send: Your name was %s!\n", message.name);
        printf("send: Your number was %d!\n", (int)message.number);
        printf("send: Your repeatID[1] was %d!\n\n", (int)message.repeatID[1]);

        /* Encode the message! */
        status = pb_encode(&stream, SimpleMessage_fields, &message);
        message_length = stream.bytes_written;
        if (!status)
        {
            printf("Encoding failed: %s\n", PB_GET_ERROR(&stream));
            return 1;
        }
    }


    /* But because we are lazy, we will just decode it immediately. */
    {
        SimpleMessage message = SimpleMessage_init_zero;
        pb_istream_t stream = pb_istream_from_buffer(buffer, message_length);

        /* Decode the message. */
        status = pb_decode(&stream, SimpleMessage_fields, &message);
        /* Check for errors */
        if (!status)
        {
            printf("Decoding failed: %s\n", PB_GET_ERROR(&stream));
            return 1;
        }

        printf("Recv: Your name was %s!\n", message.name);
        printf("Recv: Your number was %d!\n", (int)message.number);
        printf("Recv: Your repeatID[1] was %d!\n", (int)message.repeatID[1]);
    }
    return 0;
}

打印结果为:

Send: Your name was !
Send: Your number was 520!
Send: Your repeatID[1] was 222!

Recv: Your name was !
Recv: Your muber was 2!
Recv: Your repeatID[1] was 0!

出现的问题

1、 required字段name并未赋值,编码和解码都未报错。
GDB调试中,在对应的encode库函数检测这个字段不是空,所以没报错”missing required field”。
原因是不同类型的required字段都有初始默认值,string默认为空“ ”、int32为0、bool默认为false。

  1. repeated字段需手动设置数组大小:
在simple.proto中手动设置repeatID字段最大为5,
消息结构体中,该字段定义如下:
typedef struct _SimpleMessage {
    pb_size_t repeatID_count;
    int32 repeatID[5]; 
} SimpleMessage;
其中,irepeatID_count表示repeated字段实际存了几个值。

在代码中给 repeatID[5]数组赋值后,如果没有手动给repeatID_count赋值,它默认为0,解码后该repeated字段全部为0。

3.消息结构体中,optional可选字段对应有has_fieldname变量需要手动赋值:
结构体中的存在性检查变量has_number表示可选字段number是否有值,代码中如果设值number= 123,但没有手动赋值“has_number = true”,解码后默认未设置过该可选字段,即用默认值0显示。

疑问

**感觉在nanopb中,“存储重复字段的数组实际大小”、“必选字段是否已经赋值”、“可选字段是否已经设值”这些情况,都需要手动去赋值或判断,和protobuf相比这样操作太麻烦了!**
**不知道是由于nanopb是轻量化的,设计如此,还是我使用有误?

阅读 37

收藏
2024-06-18

共1个答案

小能豆

在使用 nanopb 时,确实需要手动处理一些细节,包括必选字段的检查、可选字段的存在性标志以及重复字段的计数。这些操作虽然繁琐,但都是为了保证数据在轻量级环境中的正确性和有效性。以下是详细的解答和改进建议:

1. 必选字段 (required) 的处理

在 nanopb 中,必选字段没有值时不会自动报错,需要手动检查。例如,你需要在编码前确保所有 required 字段都有正确的值。

2. 可选字段 (optional) 的处理

可选字段在赋值后需要手动设置存在性标志 (has_fieldname)。例如,赋值 number 字段后,需要手动设置 has_number

message.number = 520;
message.has_number = true;

3. 重复字段 (repeated) 的处理

在赋值 repeated 字段后,需要手动设置计数器 field_count

message.repeatID[0] = 111;
message.repeatID[1] = 222;
message.repeatID[2] = 333;
message.repeatID_count = 3;

代码示例改进

根据以上几点,对你的代码进行相应的修改:

#include <stdio.h>
#include <pb_encode.h>
#include <pb_decode.h>
#include "simple.pb.h"

int main() {
    uint8_t buffer[128];
    size_t message_length;
    bool status;

    /* Encode message */
    {
        SimpleMessage message = SimpleMessage_init_zero;
        /* Create a stream that will write to our buffer. */
        pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));

        /* Fill in the message */
        strncpy(message.name, "Lisa", sizeof(message.name) - 1); // Ensure null-termination
        message.name[sizeof(message.name) - 1] = '\0'; // Null-terminate
        message.number = 520;
        message.has_number = true;
        message.repeatID[0] = 111;
        message.repeatID[1] = 222;
        message.repeatID[2] = 333;
        message.repeatID_count = 3; // Set the count

        printf("send: Your name was %s!\n", message.name);
        printf("send: Your number was %d!\n", (int)message.number);
        printf("send: Your repeatID[1] was %d!\n\n", (int)message.repeatID[1]);

        /* Encode the message! */
        status = pb_encode(&stream, SimpleMessage_fields, &message);
        message_length = stream.bytes_written;
        if (!status) {
            printf("Encoding failed: %s\n", PB_GET_ERROR(&stream));
            return 1;
        }
    }

    /* Decode the message */
    {
        SimpleMessage message = SimpleMessage_init_zero;
        pb_istream_t stream = pb_istream_from_buffer(buffer, message_length);

        /* Decode the message. */
        status = pb_decode(&stream, SimpleMessage_fields, &message);
        /* Check for errors */
        if (!status) {
            printf("Decoding failed: %s\n", PB_GET_ERROR(&stream));
            return 1;
        }

        printf("Recv: Your name was %s!\n", message.name);
        printf("Recv: Your number was %d!\n", (int)message.number);
        printf("Recv: Your repeatID[1] was %d!\n", (int)message.repeatID[1]);
    }

    return 0;
}

总结

nanopb 的设计确实是为了在嵌入式环境中提供轻量级的 protobuf 支持,这意味着需要开发者在使用时进行更多的手动操作。这些手动操作虽然繁琐,但可以保证数据的正确性和内存的高效使用。在编码和解码过程中,务必注意必选字段的检查、可选字段的存在性标志以及重复字段的计数,确保所有数据都被正确处理。

希望这些改进能帮助你更好地使用 nanopb。如果还有其他问题,欢迎继续提问!

2024-06-18