问题概述:主要是nanopb中,optional可选、required必选、repeated重复字段的使用中,发现每个字段都需要在代码中手动处理一些东西,比如:
1.需要手动检查required字段是否有值,必选字段未赋值也不会报错;
2.给optional字段赋值后,需要手动给存在性检查变量has_fie赋值为true;
3.repeated重复字段在赋值后,需要手动赋值令field_count变量==实际数据个数;
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];
}
typedef struct simpleMessage{
char name[128l;
bool has_number; // 存在性检查变量
int32_t number;
pb_size_t repeatID_count; // 数组实际数据个数
int32_t repeatID[5];
}SimpleMessage;
#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。
在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是轻量化的,设计如此,还是我使用有误?希望有高人不吝赐教**
在nanopb中,确实需要手动处理一些字段的值,这是因为nanopb是一款轻量级的protobuf库,旨在保持简单和高效。与一些其他protobuf库相比,nanopb提供了更多的手动控制的灵活性。
下面是对你提到的问题的一些建议:
必选字段的初始默认值:
nanopb中,protobuf的必选字段会有默认值,因此没有明确赋值时,它们将包含默认值。这在设计上是为了确保对于没有赋值的字段,仍然有一个定义明确的初始状态。如果你希望强制要求必选字段的赋值,可以在编码前手动检查这些字段是否已经被设置,如果没有,可以选择报错或者给予默认值。
repeated字段的手动设置数组大小:
这是因为nanopb不会在解码时动态调整数组的大小,因此需要手动设置数组的大小。在.proto文件中,可以使用 (nanopb).max_count 来指定repeated字段的最大数量,但在实际使用中,你还需要手动设置 repeatID_count。
可选字段的存在性检查变量:
可选字段需要手动设置 has_fieldname,以指示该字段是否已经设值。nanopb并没有像某些protobuf库一样在解码时自动设置这些标志。在设值字段时,你需要手动设置对应的 has_fieldname 标志。
总体而言,nanopb的设计旨在保持轻量和简单,而牺牲了一些自动化的特性。如果你更喜欢在赋值和解码过程中更多的自动处理,你可能需要考虑使用其他protobuf库,但这可能会增加代码库的大小和复杂性。选择适合你需求的库取决于你的项目需求和性能要求。
感谢回答!看来nanopb相对protobuf来说很多功能都不是很“自动化”,需要手动处理,不是我使用的问题。
我有一个不是特别重要的想问:
最开始我使用optional可选字段时,没有手动设置has_fieldname = true,该字段的值反序列化时为0,传输不过去;
中间有两天突然好了,给optional字段赋值后,不需要手动设置has_fieldname就可以。两天以后吃了顿饭回来又不行了。
有点诡异、啼笑皆非,不知道大佬有没有碰到过?还是说has_fieldname一直都是需要手动设置的
@未必木三:
在 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++ 编译器,确保编译器版本没有引起问题。
如果上述检查都正常,但问题依然存在,可能是你所使用的库或框架存在一些特殊的实现细节。在这种情况下,查看库的文档或者寻求社区的帮助可能会更有帮助。
@Technologyforgood: 谢谢,我想您可能理解有误。我使用的是nanopb,上述所有问题全部不是针对C++ 的Protocol Buffers。