将QByteArray中的数据放入结构体中

3

我正在处理一个返回字节数组的串行设备。 在这个数组中,存储着无符号短整型和无符号字符型的值。 我有以下结构:

    typedef struct {
    unsigned short RPM;             //0
    unsigned short Intakepress;     //1
    unsigned short PressureV;       //2
    unsigned short ThrottleV;       //3
    unsigned short Primaryinp;      //4
    unsigned short Fuelc;           //5
    unsigned char Leadingign;       //6
    unsigned char Trailingign;      //7
    unsigned char Fueltemp;         //8
    unsigned char Moilp;            //9
    unsigned char Boosttp;          //10
    unsigned char Boostwg;          //11
    unsigned char Watertemp;        //12
    unsigned char Intaketemp;       //13
    unsigned char Knock;            //14
    unsigned char BatteryV;         //15
    unsigned short Speed;           //16
    unsigned short Iscvduty;        //17
    unsigned char O2volt;           //18
    unsigned char na1;              //19
    unsigned short Secinjpulse;     //20
    unsigned char na2;              //21
} fc_adv_info_t;

如何将数组映射到这种结构最好?从串行设备接收到的数组顺序与结构相匹配。


假设从线路接收到的字节序与主机字节序相匹配:如果您需要结构体在QByteArray的生命周期内保持不变,请使用好老式的memcpy。否则请使用reinterpret_cast(它将节省您的复制,但会将结构体的生命周期绑定到QByteArray本身。阅读:容易自己给自己惹麻烦)。如果字节序不匹配,则需要通过qFromLittleEndianqFromBigEndian手动转换每个字段(取决于内存格式)。 - peppe
2个回答

5
首先,使用类似C语言的语法描述结构中数据类型的方式是含糊不清的。它没有告诉我们shortchar类型的大小,也没有说明数据的字节序!一个short int不一定是16位宽,char也不总是8位!至少,你应该使用固定宽度整型或其Qt等效类型,并指定它们的字节序。
另外,typedef struct是C语言的习惯用法,在C++中是不必要的。可以去掉typedef
假设包采用大端字节序,unsigned short表示uint16_tunsigned char表示uint8_t,以下是实现方法:
struct FcAdvInfo { // this structure shouldn't be packed or anything like that!
  quint16 RPM;
  quint16 IntakePress;
  ...
  quint8 LeadingIgn;
  ...

  FcAdvInfo parse(const QByteArray &);
};

FcAdvInfo FcAdvInfo::parse(const QByteArray & src) {
  FcAdvInfo p;
  QDataStream ds(src);
  ds.setByteOrder(QDataStream::BigEndian);
  ds
    >> p.RPM
    >> p.IntakePress
    ...
    >> p.LeadingIgn
    ...
    ;
  return p;
}

最后,如果你的 struct 来自一些C代码,你必须了解它不具备可移植性,即使在相同的CPU上,如果你升级编译器,结构类型的打包和大小也会发生改变! 所以不要这样做。C/C++ 的 struct 声明除了所选择的排列方式不会导致未定义行为并且必须符合标准的其他要求(只有一些)之外,对于数据在内存中的排列方式没有任何暗示。基本就是这样。


你不觉得 QDataStream 需要额外的标题字节吗?据我所记,QDataStream 为其元数据添加了 4 个额外字节。只需澄清 QByteArray 必须在串行端口的对等方使用 QDataStream 进行序列化,这在低级设备控制器中几乎不可能发生。 - Evgeny S.
@EvgenyS。数据流本身不添加任何标头。“QByteArray必须在对等方使用QDataStream进行序列化”这是错误的。QDataStream正是为此类应用程序而设计的。它提供了可移植的方式来解析和生成基于字节的C基本类型的数据流,除其他功能外。 - Kuba hasn't forgotten Monica
@KubaOber 的打包不是完全不可预测的,如果你不对大小做任何假设,那么在这里使用 quint16 有什么用呢?请注意,char 始终是1个字节。 - Ilya

-1
首先,我要说的是,在你将结构体序列化/反序列化并与其他设备交换之前,将unsigned short类型打包到结构体中是不安全的: unsigned short通常是16位,但不能保证它是这样的,因为它取决于平台。
如果结构体成员未对齐,以至于编译器在结构体中插入填充,则情况会更糟。
如果从串口接收到的二进制数据保存在QByteArray中,并且字节顺序和“unsigned short”类型正确,则可以使用下面的代码将QByteArray中的数据映射到结构体上。请注意,仅当您的结构体已打包且其中没有填充间隙时,此代码才是正确的,请使用适用于您的编译器的结构体打包技术(请参见Structure padding and packing)。
QByteArray bArr;
bArr.resize(sizeof(fc_adv_info_t));
// do something to fill bArr with received data
fc_adv_info_t* info=reinterpret_cast<fc_adv_info_t*>(bArr.data());

是的,请使用 qint16。并且这个结构体是否保证是紧凑的(内部没有填充)? - Ilya
当然!应该考虑结构体填充。已更新帖子。 - Evgeny S.
应该没问题,因为int之间的字符数是偶数(我想)。 - Ilya
是的,在这个结构体中,只有最后一个字符似乎没有对齐。 - Evgeny S.
1
这是错误的建议。reinterpret_cast 是疯狂的。没有任何理由限制接收主机上的数据表示与在传输中的格式完全相同。在大多数情况下,这会使访问数据更加昂贵,因为每次使用数据时都必须发出代码来提取和重新对齐数据。 - Kuba hasn't forgotten Monica
.data() 是以 null 结尾的。它在 byteArray 中第一次出现 '\0' 的位置停止读取。 - Phiber

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接