C macro to C++\Qt - qt

I have the following c macro from the libpurple yahoo plugin:
#define yahoo_put16(buf, data) ( \
(*(buf) = (unsigned char)((data)>>8)&0xff), \
(*((buf)+1) = (unsigned char)(data)&0xff), \
2)
I want to implement the same as a function in my class witch would receive as a parameter a quint16 value and return it as a QByteArray
I have the following but i don't seem to get the same result as with the macro above.
QByteArray YahooPacket::packQuint16(quint16 value) const
{
QByteArray data;
data.append(QByteArray::number((value >> 8) & 0xFF));
data.append(QByteArray::number(value & 0xFF));
return data;
}
How would i do to implement my function?

QByteArray::number() creates the printable (string) version of the number which is probably not what you want. Use the QByteArray constructor that takes a buffer pointer and a size parameter. I think this will do what you want.
QByteArray YahooPacket::packQuint16(quint16 value) const
{
QByteArray data;
data.append(QByteArray(((char*)&value)+1,1));
data.append(QByteArray((char*)&value,1));
return data;
}

Related

Converting QMap<QString, QString> to Json string results empty

I have a function defined and used as this:
// usage:
QMap<QString, QString> map = ...;
foo(map);
// defination:
QString stringMapToJson(const QMap<QString, QString>& arg) {
QVariant v = QVariant::fromValue(arg);
JsonDocument doc = QJsonDocument::fromVariant(v);
...
}
Then I realized v is empty.
Is there a method to convert QMap<String, QString> to QMap<String, QVariant>, so above v could be valid?
Why above v is empty? I read people were saying QVariant and qMetaData, I don't understand given the following valid, why QString have a qMetaData problem:
QString s = "";
QVariant v = s;
(A Java programmer starts her pleasant C++ journey.)
Thanks.
There are 2 ways to do this. The first is to convert your map to a QMap<QString, QVariant> like you mentioned:
QByteArray stringMapToJson1(const QMap<QString, QString>& arg)
{
QVariantMap vmap;
for(auto it = arg.cbegin(); it != arg.cend(); ++it)
{
vmap.insert(it.key(), it.value());
}
const QVariant v = QVariant::fromValue(vmap);
const QJsonDocument doc = QJsonDocument::fromVariant(v);
return doc.toJson();
}
Alternatively, you can build the json object directly from the map. In this case it's the same amount of code:
QByteArray stringMapToJson2(const QMap<QString, QString>& arg)
{
QJsonObject jObj;
for(auto it = arg.cbegin(); it != arg.cend(); ++it)
{
jObj.insert(it.key(), it.value());
}
QJsonDocument doc;
doc.setObject(jObj);
return doc.toJson();
}
This seems like a stylistic choice and I am unsure which would be faster. Both produce the same output.
One thing to note: The conversion from QString to QVariant is predefined in Qt, so the first method works fine. For objects of your own classes you would have to register that type and provide a suitable conversion which can be a bit tough to get right. In the second method you could do this conversion inline in the loop.

QT String to char * adds extra characters

I have a qTextEdit that I grab the text from (QString) and convert to a char* with this code:
QString msgQText = ui->textMsg->toPlainText();
size_t textSize = (size_t)msgQText.size();
if (textSize > 139) {
textSize = 139;
}
unsigned char * msgText = (unsigned char *)malloc(textSize);
memcpy(msgText, msgQText.toLocal8Bit().data(), textSize);
msgText[textSize] = '\0';
if (textSize > 0) {
Msg * newTextMsg = new Msg;
newTextMsg->type = 1; // text message type
newTextMsg->bitrate = 0;
newTextMsg->samplerate = 0;
newTextMsg->bufSize = (int)textSize;
newTextMsg->len = 0;
newTextMsg->buf = (char *)malloc(textSize);
memcpy((char *)newTextMsg->buf, (char *)msgText, textSize);
lPushToEnd(sendMsgList, newTextMsg, sizeof(Msg));
ui->sendRecList->addItem((char *)newTextMsg->buf);
ui->textMsg->clear();
}
I put the text into a qListBox, but it shows up like
However, the character array, if I print it out, does not have the extra characters.
I have tried checking the "compile using UTF-8" option, but it doesn't make a difference.
Also, I send the text using RS232, and the receiver side also displays the extra characters.
The receiver code is here:
m_serial->waitForReadyRead(200);
const QByteArray data = m_serial->readAll();
if (data.size() > 0) {
qDebug() << "New serial data: " << data;
QString str = QString(data);
if (str.contains("0x6F8C32E90A")) {
qDebug() << "TEST SUCCESSFUL!";
}
return data.data();
} else {
return NULL;
}
There is a difference between the size of a QString and the size of the QByteArray returned by toLocal8Bit(). A QString contains unicode text stored as UTF-16, while a QByteArray is "just" a char[].
A QByteArray is null-terminated, so you do not need to add it manually.
As #GM pointed out: msgText[textSize] = '\0'; is undefined behavior. You are writing to the textSize + 1 position of the msgText array.
This position may be owned by something else and may be overwritten, so you end up with a non null terminated string.
This should work:
QByteArray bytes = msgQText.toLocal8Bit();
size_t textSize = (size_t)bytes.size() + 1; // Add 1 for the final '\0'
unsigned char * msgText = (unsigned char *) malloc(textSize);
memcpy(msgText, bytes.constData(), textSize);
Additional tips:
Prefer using const functions on Qt types that are copy-on-write, e.g. use QBytearray::constData() instead of QByteArray::data(). The non-const functions can cause a deep-copy of the object.
Do not use malloc() and other C-style functions if possible. Here you could do:
unsigned char * msgText = new unsigned char[textSize]; and later delete[] msgText;.
Prefer using C++ casts (static_cast, reinterpret_cast, etc.) instead of C-style casts.
You are making 2 copies of the text (2 calls to memcpy), given your code only 1 seem to be enough.

QFile write a WAV header writes only 4 byte data

I'm writing a WAV recorder, using QFile as backbone. However, when I fill my Wav struct, and try to write it to my QFile it writes only "RIFF", I viewed it with unix's od -cb 1.wav. Here is the samle code:
wavwriter.cpp
Wav::Wav(const char *fname, QFile* parent)
: QFile(fname, parent),
m_fname(fname)
{
setFileName(fname);
bool res = this->open(QIODevice::ReadWrite);
if (res) {
std::cout << "File opened for RW\n";
}
}
Wav::~Wav()
{
}
void Wav::writeHeader(const WavHdr* hdr)
{
write((char*)hdr);
flush();
}
void Wav::appendData(const QByteArray &data)
{
m_data.append(data);
}
QByteArray Wav::getWavData()
{
return m_data;
}
And the usage is as follows:
WavHdr hdr;
hdr.bits_per_sample = 8;
hdr.riff[0] = 'R';
hdr.riff[1] = 'I';
hdr.riff[2] = 'F';
hdr.riff[3] = 'F';
hdr.sample_rate = 8;
hdr.fmt[0] = 'f';
hdr.fmt[1] = 'm';
hdr.fmt[2] = 't';
m_wavs[i]->writeHeader(&hdr);
The WavHdr has the following setup:
struct WavHdr
{
char riff[4];
qint32 file_size;
char wave[4];
char fmt[4];
char len[3];
qint16 type;
quint16 format;
qint32 sample_rate;
qint32 sr_bs_channs;
quint8 bits_per_sample;
char data[4];
qint32 fsize;
};
You can't dump WavHdr to disk directly.
The way you use the write method only makes sense for zero-terminated strings. It will stop writing at the first zero-valued byte. A WavHdr is not a null-terminated string.
You cannot assume that the struct has any particular representation in memory. The compiler is free to arrange that structure the way it sees fit. Not only it can pad and align the members arbitrarily, it can also rearrange them. So that's a non-portable anti-pattern: it may happen to work on some compilers, on others it will be thoroughly broken.
Your WavHdr is wrong.
See here for reference. I've included a correct header structure below.
You probably wish to use a QSaveFile.
When saving files, you usually intend the file writing to be atomic: either it succeeds and you get a complete, valid WAV file, or it fails and nothing changes on disk (e.g. an existing file is not overwritten and corrupted). That's what QSaveFile is designed for.
You probably want your wave class to use an I/O device, but not be one.
I/O can be done with just an instance of a QIODevice*: you'll then be able to easily write the data to in-memory buffers, files, network sockets, etc. The user of your class should be free to choose what particular device to use.
Instead, use QDataStream to write the header in a portable way:
struct WavHdr
{
constexpr static quint32 k_riff_id = 0x46464952;
constexpr static quint32 k_wave_format = 0x45564157;
constexpr static quint32 k_fmt_id = 0x20746d66;
constexpr static quint32 k_data_id = 0x61746164;
// RIFF
quint32 chunk_id = k_riff_id;
quint32 chunk_size;
quint32 chunk_format = k_wave_format;
// fmt
quint32 fmt_id = k_fmt_id;
quint32 fmt_size;
quint16 audio_format;
quint16 num_channels;
quint32 sample_rate;
quint32 byte_rate;
quint16 block_align;
quint16 bits_per_sample;
// data
quint32 data_id = k_data_id;
quint32 data_size;
};
bool write(QIODevice * dev, const WavHdr & h) {
QDataStream s{dev};
s.setByteOrder(QDataStream::LittleEndian); // for RIFF
s << h.chunk_id << h.chunk_size
<< h.chunk_format;
s << h.fmt_id << h.fmt_size
<< h.audio_format
<< h.num_channels
<< h.sample_rate
<< h.byte_rate
<< h.block_align
<< h.bits_per_sample;
s << h.data_id << h.data_size;
return s.status() == QDataStream::Ok;
}

How to unpack 32bit integer packed in a QByteArray?

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?
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);
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.
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);
}
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 ) }
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>

QByteArray convert to/from unsigned char *

QByteArray inArray = " ... ";
unsigned char *in = convert1(inArray);
unsigned char *out;
someFunction(in, out);
QByteArray outArray = convert2(out);
the question is how can I correctly make these conversions (convert1 and convert2).
I cannot change someFunction(unsigned char *, unsigned char *), but I have to work with QByteArray here.
Qt has really great docs, you should use them.
If someFunction doesn't modify or store pointer to in data you can use this:
QByteArray inArray = " ... ";
unsigned char *out;
someFunction((unsigned char*)(inArray.data()), out);
QByteArray outArray((char*)out);
Otherwise you have to make a deep copy of the char* returned by QByteArray::data() (see the docs for code snippet).
if someFunction takes a const char* args then just use ConstData() or data() in QByteArray class.
if you need a char*, you can then use strdup(). This method is doing this
char *strdup (const char *s) {
char *d = malloc (strlen (s) + 1); // Space for length plus nul
if (d == NULL) return NULL; // No memory
strcpy (d,s); // Copy the characters
return d; // Return the new string
}
more info here: strdup() - what does it do in C?

Resources