首页 新闻 会员 周边

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

0
悬赏园豆:200 [待解决问题]

问题概述:主要是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是轻量化的,设计如此,还是我使用有误?希望有高人不吝赐教**
未必木三的主页 未必木三 | 初学一级 | 园豆:2
提问于:2024-01-25 19:23
< >
分享
所有回答(1)
1

在nanopb中,确实需要手动处理一些字段的值,这是因为nanopb是一款轻量级的protobuf库,旨在保持简单和高效。与一些其他protobuf库相比,nanopb提供了更多的手动控制的灵活性。

下面是对你提到的问题的一些建议:

必选字段的初始默认值:
nanopb中,protobuf的必选字段会有默认值,因此没有明确赋值时,它们将包含默认值。这在设计上是为了确保对于没有赋值的字段,仍然有一个定义明确的初始状态。如果你希望强制要求必选字段的赋值,可以在编码前手动检查这些字段是否已经被设置,如果没有,可以选择报错或者给予默认值。

repeated字段的手动设置数组大小:
这是因为nanopb不会在解码时动态调整数组的大小,因此需要手动设置数组的大小。在.proto文件中,可以使用 (nanopb).max_count 来指定repeated字段的最大数量,但在实际使用中,你还需要手动设置 repeatID_count。

可选字段的存在性检查变量:
可选字段需要手动设置 has_fieldname,以指示该字段是否已经设值。nanopb并没有像某些protobuf库一样在解码时自动设置这些标志。在设值字段时,你需要手动设置对应的 has_fieldname 标志。

总体而言,nanopb的设计旨在保持轻量和简单,而牺牲了一些自动化的特性。如果你更喜欢在赋值和解码过程中更多的自动处理,你可能需要考虑使用其他protobuf库,但这可能会增加代码库的大小和复杂性。选择适合你需求的库取决于你的项目需求和性能要求。

Technologyforgood | 园豆:5686 (大侠五级) | 2024-01-25 19:39

感谢回答!看来nanopb相对protobuf来说很多功能都不是很“自动化”,需要手动处理,不是我使用的问题。
我有一个不是特别重要的想问:
最开始我使用optional可选字段时,没有手动设置has_fieldname = true,该字段的值反序列化时为0,传输不过去;
中间有两天突然好了,给optional字段赋值后,不需要手动设置has_fieldname就可以。两天以后吃了顿饭回来又不行了。
有点诡异、啼笑皆非,不知道大佬有没有碰到过?还是说has_fieldname一直都是需要手动设置的

支持(0) 反对(0) 未必木三 | 园豆:2 (初学一级) | 2024-01-26 09:14

@未必木三:
在 Protocol Buffers 的使用中,has_fieldname 字段通常是由编译器自动生成和管理的,用于表示一个 optional 字段是否被设置过的标志。这个标志在 C++ 的 proto 类中被用来检查一个 optional 字段是否包含有效值。

通常情况下,has_fieldname 的设置和清除是由 Protocol Buffers 序列化和反序列化代码自动处理的,无需手动干预。在设置 optional 字段值时,has_fieldname 会被自动设置为 true,而在清除 optional 字段值时,has_fieldname 会被自动设置为 false。

如果你在序列化时没有手动设置 has_fieldname,而在反序列化时该字段的值为 0,可能是因为在反序列化时,has_fieldname 被设置为 false,导致解析器认为该字段没有被设置。这可能是一些实现细节或者框架的行为,可能会因为库版本、编译器版本等因素而有所变化。

如果你发现出现了不一致的情况,可以检查以下几点:

Protocol Buffers 版本: 确保你使用的 Protocol Buffers 版本是稳定的,最好是官方维护的版本。

生成的代码: 检查你的 Protocol Buffers 文件是否生成了正确的代码。可以查看生成的 C++ 代码,确认 has_fieldname 的设置和清除逻辑是否存在。

编译器: 使用兼容的 C++ 编译器,确保编译器版本没有引起问题。

如果上述检查都正常,但问题依然存在,可能是你所使用的库或框架存在一些特殊的实现细节。在这种情况下,查看库的文档或者寻求社区的帮助可能会更有帮助。

支持(0) 反对(0) Technologyforgood | 园豆:5686 (大侠五级) | 2024-01-29 20:28

@Technologyforgood: 谢谢,我想您可能理解有误。我使用的是nanopb,上述所有问题全部不是针对C++ 的Protocol Buffers。

支持(0) 反对(0) 未必木三 | 园豆:2 (初学一级) | 2024-02-01 13:54
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册