3

I'm working with a serial device that returns a byte array. In this array are values that are stored in unsigned shorts and unsigned chars. I have the following structure:

    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;

what's the best way to map the array to this structure? The order in the array received from the serial device matches the structure.

Basti An
  • 347
  • 1
  • 6
  • 23
  • Assuming that the endianess of the bytes received from the wire also matches the host endianess: just use good ol' `memcpy`, if you need the struct to survive the lifespan of your `QByteArray`. `reinterpret_cast` otherwise (it will spare you the copy, but will tie the lifetime of the struct to the QByteArray itself. Read: make it easier to shoot yourself in the foot). If the endianess doesn't match, you'll need manual conversion of each field via `qFromLittleEndian` or `qFromBigEndian` (depending on the in-memory fomat). – peppe Mar 13 '16 at 16:16

2 Answers2

5

First of all, your description of the type of data in the structure using C-like syntax is ambiguous. It tells us nothing about the size of a short or char type, nor about the endianness of the data! A short int doesn't have to be 16 bits wide, neither is char always 8 bits! At the very least, you should use the fixed width integer types, or their Qt equivalents, and specify their endianness.

Also, typedef struct is a C-ism, unnecessary in C++. Drop the typedef.

Assuming a big endian packet, unsigned short to mean uint16_t and unsigned char to mean uint8_t, here is how you could do it:

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;
}

Finally, if your struct comes from some C code, you must understand that it's not portable, and even on the same CPU, if you upgrade the compiler, the packing and the size of structure types can and will change! So don't do it. A C/C++ struct declaration implies nothing about how the data is arranged in memory, other than the chosen arrangement doesn't lead to undefined behavior, and must agree with other requirements of the standard (there are just a few). That's all, pretty much.

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • Don't you think that for QDataStream extra heading bytes are needed? As far as I remember QDataStream adds 4 extra bytes for its metadata. Just have to clarify thet the QByteArray must be serialized with QDataStream on the peer side of the serial port which is hardly a case for low level device controller. – Evgeny S. Mar 13 '16 at 18:50
  • @EvgenyS. The data stream adds no headers of its own. "QByteArray must be serialized with QDataStream on the peer side" That's false. `QDataStream` was designed precisely with this kind of an application in mind. It offers, among other functionality, a portable way to parse and generate byte-oriented data streams of C basic types. – Kuba hasn't forgotten Monica Mar 13 '16 at 19:00
  • @KubaOber packing is not completely upredictable, and what's the use of `quint16` here if you don't assume anything about sizes ? NB `char` is 1 byte, always. – Ilya Mar 13 '16 at 19:20
-1

First, I would say that is is not safe to pack unsigned short type in structures that you are going to serialize/deserialize and exchange with other devices: unsigned short is usually 16-bit, but you can't take that as guaranteed, it is platform dependent.

It is even worse if struct members are not aligned so that compiler inserts paddings in the struct.

If binary data received from serial port is kept in QByteArray and byte order and "unsigned short" types are ok then to map a data in QByteArray on the struct you can use the code below. Note, that it is only correct if your struct is packed and has no padding gaps within it, use struct packing technique for your compiler (see 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());
Community
  • 1
  • 1
Evgeny S.
  • 838
  • 4
  • 12
  • Yes, use `qint16` instead. And is it guaranteed that the struct is packed (no padding inside) ? – Ilya Mar 13 '16 at 17:41
  • Sure! Struct padding should be taken into account. Updated post. – Evgeny S. Mar 13 '16 at 18:19
  • Should be ok there, because of even number of char in between int (I think). – Ilya Mar 13 '16 at 18:23
  • Yes, in this struct only the last char seems to be not aligned. – Evgeny S. Mar 13 '16 at 18:30
  • 1
    This is bad advice. The `reinterpret_cast` is insanity. There is **absolutely no reason** to constrain the representation of the data on the receiving host to be identical to on-the-wire format. In most cases, it makes the access to the data more costly, as code has to be emitted to extract and realign the data each time you use it. – Kuba hasn't forgotten Monica Mar 13 '16 at 19:04
  • `.data()` is null terminated. it stops reading from byteArray on the first occurrence of '\0'. – Phiber Feb 24 '17 at 15:37