2

I'm working with serial communication, and I receive 32bit integers in a QByteArray, packed in 4 separate bytes (little-endian). I attempt to unpack the value from the 4 bytes using QByteArray::toLong() but it fails the conversion and returns the wrong number:

quint8 packed_bytes[] { 0x12, 0x34, 0x56, 0x78 };
QByteArray packed_array { QByteArray(reinterpret_cast<char*>(packed_bytes),
                                     sizeof(packed_bytes)) };
bool isConversionOK;
qint64 unpacked_value { packed_array.toLong(&isConversionOK) };
// At this point:
// unpacked_value == 0
// isConversionOK == false

The expected unpacked_value is 0x78563412 (little-endian unpacking). Why is the conversion failing?

DBedrenko
  • 4,871
  • 4
  • 38
  • 73

5 Answers5

4

You can use a QDataStream to read binary data.

quint8 packed_bytes[] { 0x12, 0x34, 0x56, 0x78 };
QByteArray packed_array { QByteArray(reinterpret_cast<char*>(packed_bytes), sizeof(packed_bytes)) };
QDataStream stream(packed_array);
stream.setByteOrder(QDataStream::LittleEndian);
int result;
stream >> result;
qDebug() << QString::number(result,16);
Meefte
  • 6,469
  • 1
  • 39
  • 42
3

toLong() converts a char * digits string to long. Not bytes. And your values likely don't make the up the string "0x78563412" or its decimal equivalent. Hence the 0 result.

If you need the byte values interpreted as long you can do something like:

long value;
value == *((long*)packed_bytes.data());

Or to access an array of bytes as long array:

long * values;
values == (long*)packed_bytes.data();

values[0]; // contains first long
values[1]; // contains second long
...

Don't know whether my examples work out of the box but it should make clear the principle.

Check out this example:

char bytes[] = {255, 0};

QByteArray b(bytes, 2);

QByteArray c("255");

qDebug() << b.toShort() << c.toShort();

qDebug() << *((short*)b.data()) << *((short*)c.data());

the output is:

0 255 
255 13618

You may need to change the byte order depending on the endianess. But it does what you need.

Aaron
  • 1,181
  • 7
  • 15
  • Thank you for answering me, but you see in my post that I cast my array to `char *`, which converts the integers into a string, like you describe. Am I missing something? And why does the conversion fail (as indicated by the `bool`), instead of just returning an incorrect result? – DBedrenko Apr 15 '16 at 11:16
  • Can you print that string correctly and do you see the correct value written ? Then it should work. But if the byte values represent the value ,and not their ascii representations, you need something like my approach. – Aaron Apr 15 '16 at 11:19
  • QByteArray::toLong() interprets your bytes as a \0 terminated string. – Aaron Apr 15 '16 at 11:20
  • Hmm I'm not sure why the API is working with ASCII at all or why it has to be \0 terminated, so I tried your solution, but the value returned is incorrect: "0x20000078563412" (which is wrong whether I'm unpacking the array as little-endian or big-endian). – DBedrenko Apr 15 '16 at 11:29
  • It has not to be 0 terminated by you. but methods related to ascii strings will treated as being 0\-terminated. and toLong works on strings. – Aaron Apr 15 '16 at 11:32
  • from the docs: `QByteArray can be used to store both raw bytes (including '\0's) and traditional 8-bit '\0'-terminated strings. Using QByteArray is much more convenient than using const char *. Behind the scenes, it always ensures that the data is followed by a '\0' terminator, and uses implicit sharing (copy-on-write) to reduce memory usage and avoid needless copying of data.` – Aaron Apr 15 '16 at 11:34
  • toLong()'s optional parameter "base" makes only sense with string numbers like "123". raw bytes don't need any base if assigned to another type. – Aaron Apr 15 '16 at 11:36
  • from the docs for toLong: If base is 0, the base is determined automatically using the following rules: `If the byte array begins with "0x", it is assumed to be hexadecimal; if it begins with "0", it is assumed to be octal; otherwise it is assumed to be decimal.` So Qt actually looks for strings to determine the base if not given. – Aaron Apr 15 '16 at 11:37
  • Also check out `QByteArray QByteArray::number ( int n, int base = 10 ) [static]` which is the reverse function. – Aaron Apr 15 '16 at 11:41
  • Hmm, thanks for the explanation Aaron, but I still can't successfully unpack the value from the array of bytes... unless you're suggesting I should convert the 4 bytes into a string? But that seems unnecessary, especially when I'm trying to work with numbers which are far easier to work with than strings. – DBedrenko Apr 15 '16 at 11:47
  • Added a working example for short numbers. Depending on endianess you may need to reverse the byte order. – Aaron Apr 15 '16 at 11:57
  • Note that using casts to unpack values like this is not guaranteed to work, see [this answer](http://stackoverflow.com/a/36772288/797744). – DBedrenko Apr 22 '16 at 07:14
1

you can build your qint64 with bit manipulators:

#include <QtGlobal>
#include <QByteArray>
#include <QDebug>

int main()
{
    quint8 packed_bytes[] { 0x12, 0x34, 0x56, 0x78 };
    QByteArray packed_array { QByteArray(reinterpret_cast<char*>(packed_bytes),
                                         sizeof(packed_bytes)) };

    qint64 unpacked_value = 0;

    unpacked_value |=   packed_array.at(0)       |
                        packed_array.at(1) << 8  |
                        packed_array.at(2) << 16 |
                        packed_array.at(3) << 24;

    qDebug() << QString("0x%1").arg(unpacked_value, 0, 16);
}
Paraboloid87
  • 428
  • 2
  • 14
  • Thanks for the solution, but it's rather low level, only works to produce unsigned numbers, and I'm not sure if I can use it to convert e.g. 2-byte or 3-byte values. Surely a framework as huge as Qt already has functions that abstract away low-level bitshifting? Plus I would have to write testcases for this, so it would definitely be better to use something already in Qt. – DBedrenko Apr 15 '16 at 11:51
0

Here's a generic solution for converting a QByteArray to "some other type" (such as what is specifically asked in the question) by running it through a QDataStream (as done by the accepted answer).

DISCLAIMER: I am only advocating for using this in a private implementation. I am aware there are many ways one could abuse the macro!

Using this macro, you can easily produce many conversion functions such as the examples I've provided. Defining a series of such functions in this way may be useful if you need to pull a variety of types out of a stream. Obviously, you could tweak the macro for your use case, the point is the pattern can remain basically same and be put in a macro like this.

#define byteArrayToType( data, order, type ) \
    QDataStream stream( data ); \
    stream.setByteOrder( order ); \
    type t; \
    stream >> t; \
    return t;

Example functions, which simply wrap the macro:

16 bit, signed

qint16 toQInt16( const QByteArray &data,
                 const QDataStream::ByteOrder order=QDataStream::BigEndian )
{ byteArrayToType( data, order, qint16 ) }

32 bit, signed

qint32 toQInt32( const QByteArray &data,
                 const QDataStream::ByteOrder order=QDataStream::BigEndian )
{ byteArrayToType( data, order, qint32 ) }

64 bit, signed

qint64 toQInt64( const QByteArray &data,
                 const QDataStream::ByteOrder order=QDataStream::BigEndian )
{ byteArrayToType( data, order, qint64 ) }
BuvinJ
  • 10,221
  • 5
  • 83
  • 96
0

Cast the Byte array to the required format and use the built-in function qFromBigEndian or qFromLittleEndian to set the Byte order. Example code is shown below,

QByteArray byteArray("\x45\x09\x03\x00");
quint32 myValue = qFromBigEndian<quint32>(byteArray);
qDebug() << "Hex value: " << QString("0x%1").arg(myValue, 8, 16, QLatin1Char( '0' ));

myValue holds the converted value.

Don't forget to include the header file <QtEndian>