Does Qt have a QIODevice pair that would work for intra-process point-to-point communications?
One could use the concrete QTCPSocket or QLocalSocket, but the server-side connection API is a bit cumbersome, and it seems wasteful to force the data through the OS.
The following is a usable, rudimentary implementation. It uses an internal signal-slot pair to push the data to the other endpoint. That way either end of the connection can live in any thread, and the ends can be moved between threads without losing data or inducing any races.
The private QRingBuffer is used in lieu of reinventing the wheel. Add QT += core-private to the .pro file to make that available. Qt PIMPL is used to gain access to the internal device buffers in Qt versions 5.7 and above.
If you wish to instantiate an open pipe, as may often be the case, you can pass the I/O mode to the constructor. Typical use:
int main(/*…*/)
{
/*…*/
AppPipe end1 { QIODevice::ReadWrite };
AppPipe end2 { &end1, QIODevice::ReadWrite };
AppPipe end3 { &end1, QIODevice::ReadOnly };
// the pipes are open ready to use
/*…*/
}
Whatever you write to one pipe, ends up as readable data in the other, connected pipes, and vice versa. In the above example, data written to end1 is readable from both end2 and end3 independently. Data written to end2 is readable from end1. end3 is effectively a listen-only pipe. Connection of additional pipes is cheap - there's no O(N) cost associated with sending big chunks of data to multiple pipes, because read QRingBuffers of connected pipes store shallow copies of entire byte arrays sent from the originating pipe.
All of the QIODevice semantics hold - you can connect to the readyRead signal, use the pipe with a QDataStream or a QTextStream, etc. As with any QIODevice, you can only use the class from its thread(), but the other endpoint can live in any thread, and both can be moved between threads as desired, without loss of data.
If the other pipe end is not open and readable, the writes are no-ops, even though they succeed. Closing the pipe clears the read and write buffers, so that it can be re-opened for reuse.
The pipe buffers write data by default, and the write buffer can be forcibly flushed using AppPipe::flush(), unless opened in QIODevice::Unbuffered mode.
The hasIncoming and hasOutgoing signals are useful in monitoring the data going over the pipe.
// https://github.com/KubaO/stackoverflown/tree/master/questions/local-pipe-32317081
// This project is compatible with Qt 4 and Qt 5
#include <QtTest>
#include <private/qiodevice_p.h>
#include <private/qringbuffer_p.h>
#include <algorithm>
#include <climits>
#ifndef Q_DECL_OVERRIDE
#define Q_DECL_OVERRIDE
#endif
class AppPipePrivate : public QIODevicePrivate {
public:
#if QT_VERSION < QT_VERSION_CHECK(5,7,0)
QRingBuffer buffer;
QRingBuffer writeBuffer;
int writeBufferChunkSize;
#endif
const QByteArray *writeData;
AppPipePrivate() : writeData(0) { writeBufferChunkSize = 4096; }
};
/// A simple point-to-point intra-process pipe. The other endpoint can live in any
/// thread.
class AppPipe : public QIODevice {
Q_OBJECT
Q_DECLARE_PRIVATE(AppPipe)
static inline int intLen(qint64 len) { return std::min(len, qint64(INT_MAX)); }
Q_SLOT void _a_write(const QByteArray &data) {
Q_D(AppPipe);
if (!(d->openMode & QIODevice::ReadOnly)) return; // We must be readable.
d->buffer.append(data); // This is a chunk shipped from the source.
emit hasIncoming(data);
emit readyRead();
}
void hasOutgoingLong(const char *data, qint64 len) {
while (len) {
int const size = intLen(len);
emit hasOutgoing(QByteArray(data, size));
data += size;
len -= size;
}
}
public:
AppPipe(QIODevice::OpenMode mode, QObject *parent = 0) :
QIODevice(*new AppPipePrivate, parent) {
open(mode);
}
AppPipe(AppPipe *other, QIODevice::OpenMode mode, QObject *parent = 0) :
QIODevice(*new AppPipePrivate, parent) {
open(mode);
addOther(other);
}
AppPipe(AppPipe *other, QObject *parent = 0) :
QIODevice(*new AppPipePrivate, parent) {
addOther(other);
}
~AppPipe() Q_DECL_OVERRIDE {}
void addOther(AppPipe *other) {
if (other) {
connect(this, SIGNAL(hasOutgoing(QByteArray)), other, SLOT(_a_write(QByteArray)), Qt::UniqueConnection);
connect(other, SIGNAL(hasOutgoing(QByteArray)), this, SLOT(_a_write(QByteArray)), Qt::UniqueConnection);
}
}
void removeOther(AppPipe *other) {
disconnect(this, SIGNAL(hasOutgoing(QByteArray)), other, SLOT(_a_write(QByteArray)));
disconnect(other, SIGNAL(hasOutgoing(QByteArray)), this, SLOT(_a_write(QByteArray)));
}
void flush() {
Q_D(AppPipe);
while (!d->writeBuffer.isEmpty()) {
QByteArray const data = d->writeBuffer.read();
emit hasOutgoing(data);
emit bytesWritten(data.size());
}
}
void close() Q_DECL_OVERRIDE {
Q_D(AppPipe);
flush();
QIODevice::close();
d->buffer.clear();
}
qint64 write(const QByteArray &data) { // This is an optional optimization. The base method works OK.
Q_D(AppPipe);
QScopedValueRollback<const QByteArray*> back(d->writeData);
if (!(d->openMode & Text))
d->writeData = &data;
return QIODevice::write(data);
}
qint64 writeData(const char *data, qint64 len) Q_DECL_OVERRIDE {
Q_D(AppPipe);
bool buffered = !(d->openMode & Unbuffered);
if (buffered && (d->writeBuffer.size() + len) > d->writeBufferChunkSize)
flush();
if (!buffered
|| len > d->writeBufferChunkSize
|| (len == d->writeBufferChunkSize && d->writeBuffer.isEmpty()))
{
if (d->writeData && d->writeData->data() == data && d->writeData->size() == len)
emit hasOutgoing(*d->writeData);
else
hasOutgoingLong(data, len);
}
else
memcpy(d->writeBuffer.reserve(len), data, len);
return len;
}
bool isSequential() const Q_DECL_OVERRIDE { return true; }
Q_SIGNAL void hasOutgoing(const QByteArray &);
Q_SIGNAL void hasIncoming(const QByteArray &);
#if QT_VERSION >= QT_VERSION_CHECK(5,7,0)
// all the data is in the read buffer already
qint64 readData(char *, qint64) Q_DECL_OVERRIDE { return 0; }
#else
qint64 readData(char *data, qint64 len) Q_DECL_OVERRIDE {
Q_D(AppPipe);
qint64 hadRead = 0;
while (len && !d->buffer.isEmpty()) {
int size = d->buffer.read(data, intLen(len));
hadRead += size;
data += size;
len -= size;
}
return hadRead;
}
bool canReadLine() const Q_DECL_OVERRIDE {
Q_D(const AppPipe);
return d->buffer.indexOf('\n') != -1 || QIODevice::canReadLine();
}
qint64 bytesAvailable() const Q_DECL_OVERRIDE {
Q_D(const AppPipe);
return QIODevice::bytesAvailable() + d->buffer.size();
}
qint64 bytesToWrite() const Q_DECL_OVERRIDE {
Q_D(const AppPipe);
return QIODevice::bytesToWrite() + d->writeBuffer.size();
}
#endif
};
// ...
#include "main.moc"
A minimal test harness:
class TestAppPipe : public QObject {
Q_OBJECT
QByteArray data1, data2;
struct PipePair {
AppPipe end1, end2;
PipePair(QIODevice::OpenMode mode = QIODevice::NotOpen) :
end1(QIODevice::ReadWrite | mode), end2(&end1, QIODevice::ReadWrite | mode) {}
};
Q_SLOT void initTestCase() {
data1 = randomData();
data2 = randomData();
}
Q_SLOT void sizes() {
QCOMPARE(sizeof(AppPipe), sizeof(QIODevice));
}
Q_SLOT void basic() {
PipePair p;
QVERIFY(p.end1.isOpen() && p.end1.isWritable() && p.end1.isReadable());
QVERIFY(p.end2.isOpen() && p.end2.isWritable() && p.end2.isReadable());
static const char hello[] = "Hello There!";
p.end1.write(hello);
p.end1.flush();
QCOMPARE(p.end2.readAll(), QByteArray(hello));
}
static QByteArray randomData(int const size = 1024*1024*32) {
QByteArray data;
data.resize(size);
char *const d = data.data();
for (char *p = d+data.size()-1; p >= d; --p)
*p = qrand();
Q_ASSERT(data.size() == size);
return data;
}
static void randomChunkWrite(AppPipe *dev, const QByteArray &payload) {
for (int written = 0, left = payload.size(); left; ) {
int const chunk = std::min(qrand() % 82931, left);
dev->write(payload.mid(written, chunk));
left -= chunk; written += chunk;
}
dev->flush();
}
void runBigData(PipePair &p) {
Q_ASSERT(!data1.isEmpty() && !data2.isEmpty());
randomChunkWrite(&p.end1, data1);
randomChunkWrite(&p.end2, data2);
QCOMPARE(p.end1.bytesAvailable(), qint64(data2.size()));
QCOMPARE(p.end2.bytesAvailable(), qint64(data1.size()));
QCOMPARE(p.end1.readAll(), data2);
QCOMPARE(p.end2.readAll(), data1);
}
Q_SLOT void bigDataBuffered() {
PipePair p;
runBigData(p);
}
Q_SLOT void bigDataUnbuffered() {
PipePair p(QIODevice::Unbuffered);
runBigData(p);
}
Q_SLOT void cleanupTestCase() {
data1.clear(); data2.clear();
}
};
QTEST_MAIN(TestAppPipe)
# local-pipe-32317081.pro
QT = core
greaterThan(QT_MAJOR_VERSION, 4): QT = core-private testlib
else: CONFIG += qtestlib
DEFINES += \
QT_DEPRECATED_WARNINGS \
QT_DISABLE_DEPRECATED_BEFORE=0x060000 \
QT_RESTRICTED_CAST_FROM_ASCII
CONFIG += console c++14
CONFIG -= app_bundle
TEMPLATE = app
SOURCES = main.cpp
Related
I am using qDebug(), qInfo() and so on in combination of qInstallMessageHandler to write my logfiles. I also get an output on my batch screen, when executing my application.
I only found QT_NO_DEBUG_OUTPUT, but I want to toggle this configuration while run-time. Is there a way to prevent Qt from writing to std output?
sadly no, you only get access to the messages, but cannot prevent from beeing written to std output.
That's false in Qt 5 at the very least. The message printing code looks as follows: you can clearly see that only the message handler is being used:
if (grabMessageHandler()) {
// prefer new message handler over the old one
if (msgHandler.load() == qDefaultMsgHandler
|| messageHandler.load() != qDefaultMessageHandler) {
(*messageHandler.load())(msgType, context, message);
} else {
(*msgHandler.load())(msgType, message.toLocal8Bit().constData());
}
ungrabMessageHandler();
} else {
fprintf(stderr, "%s\n", message.toLocal8Bit().constData());
}
As you can see, the fprintf(stderr, ...) is a fallback only if recursion is detected from within the messageHandler itself.
Thus, all you need to prevent any debug output is to implement and set your own messageHandler.
To completely turn off all the debug output in Qt, the following works:
#include <QtCore>
int main() {
qDebug() << "I'm not quiet at all yet";
qInstallMessageHandler(+[](QtMsgType, const QMessageLogContext &, const QString &){});
qDebug() << "I'm very, very quiet";
}
In any case, a sensible implementation of an application-wide file logger might look as follows - it doesn't recreate the QTextStream unnecessarily; it uses QString::toUtf8() instead, and explicitly writes line endings.
#include <QtCore>
class Logger {
static struct Data {
Logger *instance;
QtMessageHandler chainedHandler;
} d;
bool m_isOpen;
QFile m_logFile;
QtMessageHandler m_oldHandler = {};
static void handler(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
if (d.instance)
d.instance->log(msg);
if (d.chainedHandler)
d.chainedHandler(type, context, msg);
}
public:
enum ChainMode { DontChain, Chain };
Logger() {
Q_ASSERT(!instance());
m_logFile.setFileName("myLog.txt");
m_isOpen = m_logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text);
d.instance = this;
}
~Logger() { uninstallHandler(); }
bool isOpen() const { return m_isOpen; }
void installHandler(ChainMode mode) {
Q_ASSERT(!m_oldHandler);
m_oldHandler = qInstallMessageHandler(handler);
if (mode == Chain)
d.chainedHandler = m_oldHandler;
}
void uninstallHandler() {
if (m_oldHandler) {
m_oldHandler = nullptr;
d.chainedHandler = nullptr;
qInstallMessageHandler(m_oldHandler);
}
}
/// This method is *not* thread-safe. Use with a thread-safe wrapper such as `qDebug`.
void log(const QString & msg) {
if (isOpen()) {
m_logFile.write(msg.toUtf8());
m_logFile.write("\n", 1);
}
}
/// Closes the log file early - this is mostly used for testing.
void endLog() {
uninstallHandler();
m_logFile.close();
}
static Logger *instance() { return d.instance; }
};
Logger::Data Logger::d;
template <typename T> QByteArray streamOutputFor(const T &data) {
QBuffer buf;
buf.open(QIODevice::ReadWrite | QIODevice::Text);
QTextStream s(&buf);
s << data << endl;
buf.close();
return buf.data();
}
QByteArray readEnd(const QString &fileName, int count) {
QFile file(fileName);
if (file.open(QIODevice::ReadOnly | QIODevice::Text) && file.size() >= count) {
file.seek(file.size() - count);
return file.readAll();
}
return {};
}
void test() {
auto const entry = QDateTime::currentDateTime().toString().toUtf8();
Q_ASSERT(Logger::instance()->isOpen());
qDebug() << entry.data();
Logger::instance()->endLog();
auto reference = streamOutputFor(entry.data());
auto readback = readEnd("myLog.txt", reference.size());
Q_ASSERT(!reference.isEmpty());
Q_ASSERT(readback == reference);
}
int main() {
Logger logger;
logger.installHandler(Logger::DontChain);
qDebug() << "Something or else";
test();
}
I have created a wrapper library around QSerialPort. I want to communicate with my device. First, I send list command to my device and it should return list of commands supported by that device. However, while debugging my code, i observed that list command is being send to the device and device returns the proper response to it (i debugged using serial traffic sniffer Linux tools). However, i am not getting any response from my device using QSerialPort (while serial traffic sniffer tool was disable). I am unable to get it work after testing it several times.
My Serial.h:
class Serial : public Print {
public:
Serial();
explicit Serial(const char *dev_path);
~Serial();
int begin(unsigned long baudrate);
int begin(unsigned long baudrate, uint8_t cfg);
void end(void);
int available(void) const;
bool availableForWrite(void) const;
void flush(void);
bool isError(void) const;
void reset(void);
unsigned long write(uint8_t c);
unsigned long write(uint8_t *p_data, unsigned long maxSize);
int read(void);
void close();
QSerialPort &getPort()
{
return *_p_port;
}
public slots:
void readyBe(void);
private:
QSerialPort *_p_port;
unsigned long _baudrate;
};
My Serial.cpp:
Serial::Serial()
{
_p_port = new QSerialPort();
if (_p_port == nullptr)
throw std::runtime_error("Can't allocate memory");
}
Serial::Serial(const char *dev_path)
{
_p_port = new QSerialPort(QString(dev_path), QApplication::instance());
if (_p_port == nullptr)
throw std::runtime_error("Can't allocate memory");
// _p_port->setPortName(QString(dev_path));
if (_p_port->open(QIODevice::ReadWrite) == false) {
throw std::runtime_error("Can't open the serial _p_port");
delete _p_port;
}
_p_port->setBaudRate(QSerialPort::Baud9600);
_p_port->setDataBits(QSerialPort::Data8);
_p_port->setParity(QSerialPort::NoParity);
_p_port->setStopBits(QSerialPort::OneStop);
_p_port->setFlowControl(QSerialPort::NoFlowControl);
}
Serial::~Serial()
{
if (_p_port != nullptr) {
end();
delete _p_port;
}
}
int Serial::begin(unsigned long baudrate)
{
if (_p_port->setBaudRate(baudrate, QSerialPort::AllDirections) == false)
return -1;
_baudrate = baudrate;
return 0;
}
void Serial::end()
{
if (_p_port->isOpen())
_p_port->close();
}
int Serial::available(void) const
{
int num_bytes = _p_port->bytesAvailable();
return num_bytes;
}
bool Serial::availableForWrite(void) const
{
if (_p_port->isWritable())
return true;
return false;
}
void Serial::flush()
{
_p_port->flush();
}
unsigned long Serial::write(uint8_t c)
{
if (_p_port->putChar(c))
return 1;
return 0;
}
unsigned long Serial::write(uint8_t *p_data, unsigned long maxSize)
{
return _p_port->write(reinterpret_cast<const char *>(p_data), (qint64)maxSize);
}
int Serial::read(void)
{
char c;
_p_port->getChar(&c);
return c;
}
void Serial::reset(void)
{
_p_port->clear(QSerialPort::AllDirections);
_p_port->clearError();
}
bool Serial::isError(void) const
{
if (_p_port->error() == QSerialPort::NoError)
return false;
return true;
}
And my main.cpp:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
// w.show();
Serial serial("ttyACM0");
if (serial.begin(115200))
std::cout << "Failed to set Baud rate" << std::endl;
std::cout << "Sending data" << std::endl;
// QObject::connect(&(serial.getPort()), SIGNAL(readyRead()), &serial, SLOT(readyBe()));
serial.print("list\r");
serial.flush();
while (true) {
while (true) {
while (serial.available() == 0) {
if (serial.isError()) {
std::cout << "Error" << std::endl;
// serial.reset();
}
}
char c = serial.read();
std::cout << c;
if (c == '\n')
break;
}
std::cout << std::endl;
}
return a.exec();
}
You've pretty much missed everything needed for this code to work: the event loop. I/O in real life is asynchronous. You can't just "read" from the port without having some means of getting informed when the data is available, and actually letting the asynchronous I/O requests get processed. Yes, there are some legacy APIs that let you do that, but they mostly lead to spaghetti code, wasted threads, and poor performance.
The while (serial.available() == 0) loop is a no-op. It does nothing to actually let the available() return any other value. All that available() does internally is read an integer member of a class. You never run any code that could update the value stored in that member. Even if you would convert this to serial.waitForReadyRead(), which does update the number of available bytes, you're still not spinning an event loop, and thus you won't be able to process timeouts, or react to any other events an application might need to react to. QIODevice::waitForReadyRead is only meant to do one thing: return when a readyRead signal would fire. It won't process any other events, and it's a crutch used to port blocking code and is not really meant for production use.
You should redesign your code to be asynchronous and driven by signals from QSerialPort. The work will then be done from within QCoreApplication::exec - you won't have a loop of your own. This inversion of control is critical for getting async I/O working.
See e.g. this answer for a very simple example of an async approach, and this answer for a more complete one.
I have a Client/Server based Qt application, using QTcpServer and QTcpSocket, I managed to do the connection and send some data back and forth between the client and the server.
The client sends many types of data to the server (string, int, files and a real time audio stream) and since my server impliment a single data input SLOT (readyRead()):
connect(socket, SIGNAL(readyRead()),this, SLOT(readyRead()));
I don't know how could I distinguish between all this received data and call respectively the right function in the server.
Example (in the server):
- if I receive string => call function showData(QString data);
- if I receive file => call function saveFile(QFile file);
- if I receive audio stream => play audio stream
- ...
SERVER:
void Server::newClientConnection()
{
QTcpSocket *socket = server->nextPendingConnection();
connect(socket, SIGNAL(readyRead()), this, SLOT(readyRead()));
//...
}
void Server::readyRead()
{
QTcpSocket *clientSocket = qobject_cast<QTcpSocket *>(sender());
if (clientSocket == 0) {
return;
}
QDataStream in(clientSocket);
if (sizeMessageClient == 0)
{
if (clientSocket->bytesAvailable() < (int)sizeof(quint16)){
return;
}
in >> sizeMessageClient;
}
if (clientSocket->bytesAvailable() < sizeMessageClient) {
return;
}
sizeMessageClient = 0;
in >> data;
/*
I don't know the type of the received data !!
- if I receive string => call function showData(QString data);
- if I receive file => call function saveFile(QFile file);
- if I receive audio stream => play audio stream
- ...
*/
}
CLIENT:
Client::Client()
{
socket = new QTcpSocket(this);
connect(socket, SIGNAL(readyRead()), this, SLOT(readyRead()));
sizeMessageServer = 0;
}
void Client::readyRead()
{
QDataStream in(socket);
if (sizeMessageServer == 0)
{
if (socket->bytesAvailable() < (int)sizeof(quint16)) {
return;
}
in >> sizeMessageServer;
}
if (socket->bytesAvailable() < sizeMessageServer) {
return;
}
int messageReceived;
in >> messageReceived;
messageReceived = static_cast<int>(messageReceived);
sizeMessageServer = 0;
switch(messageReceived)
{
case 1:
qDebug() << "send a File";
sendFile();
break;
case 2:
qDebug() << "send a string data";
sendStringData();
break;
case 3:
qDebug() << "stream audio to the server";
streamAudioToServer();
break;
case n:
// ...
}
}
I am not looking for a complete solution, all I am looking for is some guidance in the right direction.
The implementation of your protocol doesn't fully leverage QDataStream in Qt 5.7. Here's how it might look now - it can be quite simple.
First, let's define the requests we know of:
enum class Req : quint32 {
Unknown, String, File
};
Q_DECLARE_METATYPE(Req)
QDataStream & operator<<(QDataStream & ds, Req req) {
return ds << (quint32)req;
}
QDataStream & operator>>(QDataStream & ds, Req & req) {
quint32 val;
ds >> val;
if (ds.status() == QDataStream::Ok)
req = Req(val);
return ds;
}
It'd also be handy to have a transaction RAII helper.
struct Transaction {
QDataStream & stream;
Transaction(QDataStream & stream) : stream{stream} {
stream.startTransaction();
}
~Transaction() {
stream.commitTransaction();
}
bool ok() {
return stream.status() == QDataStream::Ok;
}
};
The client receives requests from the server and signals the need to reply with data. The code that uses the client would react to these signals and reply back by invoking a matching slot. E.g.
void clientUser(Client & client) {
QObject::connect(&client, &Client::needString, &client, [&]{
client.sendString(QStringLiteral{"You got string!"});
});
And:
class Client : public QObject {
Q_OBJECT
QIODevice & m_dev;
QDataStream m_str{&m_dev};
void onReadyRead() {
Transaction tr{m_str};
Req req;
m_str >> req;
if (!tr.ok()) return;
if (req == Req::String)
emit needString();
else if (req == Req::File) {
QString fileName;
m_str >> fileName;
if (!tr.ok()) return;
emit needFile(fileName);
}
else emit unknownRequest(req);
}
public:
Client(QIODevice & dev) : m_dev{dev} {
connect(&m_dev, &QIODevice::readyRead, this, &Client::onReadyRead);
}
Q_SIGNAL void unknownRequest(Req);
Q_SIGNAL void needString();
Q_SIGNAL void needFile(const QString & fileName);
Q_SLOT void sendString(const QString & str) {
m_str << Req::String << str;
}
Q_SLOT void sendFile(const QString & fileName, const QByteArray & data) {
m_str << Req::File << fileName << data;
}
};
The server is very similar. Its user sends the request to a client via request slots. Once the server hears back from the client, it indicates it through the has signals:
class Server : public QObject {
Q_OBJECT
QIODevice & m_dev;
QDataStream m_str{&m_dev};
void onReadyRead() {
Transaction tr{m_str};
Req req;
m_str >> req;
if (!tr.ok()) return;
if (req == Req::String) {
QString str;
m_str >> str;
if (!tr.ok()) return;
emit hasString(str);
}
else if (req == Req::File) {
QString fileName;
QByteArray data;
m_str >> fileName >> data;
if (!tr.ok()) return;
emit hasFile(fileName, data);
}
else emit hasUnknownRequest(req);
}
public:
Server(QIODevice & dev) : m_dev{dev} {
connect(&m_dev, &QIODevice::readyRead, this, &Server::onReadyRead);
}
Q_SIGNAL void hasUnknownRequest(Req);
Q_SIGNAL void hasString(const QString &);
Q_SIGNAL void hasFile(const QString & name, const QByteArray &);
Q_SLOT void requestString() {
m_str << Req::String;
}
Q_SLOT void requestFile(const QString & name) {
m_str << Req::File << name;
}
};
Qt 5.7
According to the documentation, QAudioDecoder does not support streaming media. But is accepts either a filename or a QIODevice as source. Came the "bright idea": let's subclass QIODevice to create streaming media support.
But I always get an error:
Unable to start decoding process. Stream contains no data.
I have checked and double checked and debugged. Can't figure out what can be the problem.
I'm only testing it, so the code below is by no means final.
header:
class InputBuffer : public QIODevice
{
public:
InputBuffer(QString fileName);
~InputBuffer();
qint64 readData(char *data, qint64 maxlen);
qint64 writeData(const char *data, qint64 len);
bool isSequential();
qint64 bytesAvailable();
qint64 size();
private:
char* bufferRef;
qint64 length;
qint64 position;
};
implementation:
#include "inputbuffer.h"
InputBuffer::InputBuffer(QString fileName)
{
QFileInfo fileInfo(fileName);
length = fileInfo.size();
position = 0;
bufferRef = (char*)malloc(length);
QFile file(fileName);
file.open(QFile::ReadOnly);
file.read(bufferRef, length);
file.close();
emit readyRead();
}
InputBuffer::~InputBuffer()
{
free(bufferRef);
}
qint64 InputBuffer::readData(char *data, qint64 maxlen)
{
if (position >= length) {
return -1;
}
qint64 readSize = qMin(maxlen, length - position);
memcpy(data, bufferRef + position, readSize);
position += readSize;
return readSize;
}
qint64 InputBuffer::writeData(const char *data, qint64 len)
{
return -1;
}
bool InputBuffer::isSequential()
{
return true;
}
qint64 InputBuffer::bytesAvailable()
{
return length - position;
}
qint64 InputBuffer::size()
{
return length;
}
usage:
inputBufferRef = new InputBuffer("/home/pp/Zenék/Test.mp3");
inputBufferRef->open(QIODevice::ReadOnly);
audioDecoderRef = new QAudioDecoder();
audioDecoderRef->setAudioFormat(audioFormat);
audioDecoderRef->setSourceDevice(inputBufferRef);
connect(audioDecoderRef, SIGNAL(bufferReady()), this, SLOT(decoderBufferReady()));
connect(audioDecoderRef, SIGNAL(finished()), this, SLOT(decoderFinished()));
connect(audioDecoderRef, SIGNAL(error(QAudioDecoder::Error)), this, SLOT(decoderError(QAudioDecoder::Error)));
audioDecoderRef->start();
None of your isSequential(), bytesAvailable(), nor size() methods override the respective method in QIODevice, because the signatures don't match. They are all missing a const qualifier. Using the C++11 override keyword when overriding virtual methods helps.
Say I have a function that creates a QIODevice (e.g. a QFile), then returns a pointer to a QDataStream constructed from the QIODevice. What is the best way to deal with the memory allocation here? Clearly the QIODevice must be heap allocated to stay available to the QDataStream upon function termination, however the destruction of a QDataStream does not destroy or close the device. Is there a standard way of dealing with this seemingly common problem?
Ideally I want a function that returns an object (not a pointer to an object) that behaves like a QDataStream but upon destruction closes the device. Effectively a standard library input stream.
Example code:
QDataStream* getStream(const QString& filename) {
QFile* file = new QFile(filename); // needs to be explicitly deleted later
file->open(QIODevice::ReadOnly);
QDataStream* out = new QDataStream(&file); // same here
return out;
}
std::shared_ptr<QDataStream> getStream(const QString& filename)
{
QFile* file = new QFile(filename); // needs to be explicitly deleted later
file->open(QIODevice::ReadOnly);
std:shared_ptr<QDataStream> out(new QDataStream(&file), QDSDeleter);
return out;
}
void QDSDeleter(QDataStream* s)
{
QIODevice* device = s->device();
device->close();
delete device;
}
std::unique_ptr is another option depending on your needs; here's a reference for the former if you need it.
Edit: Qt also has the facility for this with its QSharedPointer class, where you can also supply a deleter as a constructor argument. Other pointer wrapper options are given there. Thanks #RA. for the correction.
QDataStream has a handy owndev private member that you can set to true to have the stream effectively own the device. The stream can also be easily made moveable - bringing it very close to your requirement of it behaving like a value. Ideally, you would modify your copy of Qt to implement it, but it can also be worked around.
// https://github.com/KubaO/stackoverflown/tree/master/questions/qdatastream-move-own-13039614
#include <QDataStream>
class DataStream : public QDataStream {
struct Proxy {
QScopedPointer<QDataStreamPrivate> d;
QIODevice *dev;
bool owndev;
bool noswap;
QDataStream::ByteOrder byteorder;
int ver;
QDataStream::Status q_status;
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
virtual ~Proxy();
#endif
};
static Proxy *p(QDataStream *ds) { return reinterpret_cast<Proxy *>(ds); }
static const Proxy *p(const QDataStream *ds) {
return reinterpret_cast<const Proxy *>(ds);
}
#if defined(QT_TESTLIB_LIB) || defined(QT_MODULE_TEST)
friend class DataStreamTest;
#endif
public:
DataStream() = default;
using QDataStream::QDataStream;
DataStream(DataStream &&other) : DataStream(static_cast<QDataStream &&>(other)) {}
DataStream(QDataStream &&other) {
using std::swap;
Proxy &o = *p(&other);
Proxy &t = *p(this);
swap(t.d, o.d);
swap(t.dev, o.dev);
swap(t.owndev, o.owndev);
swap(t.noswap, o.noswap);
swap(t.byteorder, o.byteorder);
swap(t.ver, o.ver);
swap(t.q_status, o.q_status);
}
DataStream &operator=(DataStream &&other) {
return *this = static_cast<QDataStream &&>(other);
}
DataStream &operator=(QDataStream &&other) {
this->~DataStream();
new (this) DataStream(std::move(other));
return *this;
}
void setOwnedDevice(QIODevice *dev) {
setDevice(dev);
p(this)->owndev = true;
}
bool ownsDevice() const { return p(this)->owndev; }
static bool ownsDevice(const QDataStream *ds) { return p(ds)->owndev; }
};
Since QObject has a built-in reference count, we could also have the QDataStream act as a shared pointer for it if we wished to.
The following requirements are tested on both Qt 4.8 and 5.10:
PASS : DataStreamTest::isBinaryCompatible()
PASS : DataStreamTest::streams()
PASS : DataStreamTest::movesFromNotOwnedQDataStream()
PASS : DataStreamTest::movesFromNotOwnedDataStream()
PASS : DataStreamTest::assignsFromNotOwnedQDataStream()
PASS : DataStreamTest::assignsFromNotOwnedDataStream()
PASS : DataStreamTest::returnsFromNotOwnedQDataStream()
PASS : DataStreamTest::returnsFromNotOwnedDataStream()
PASS : DataStreamTest::movesFromOwnedQDataStream()
PASS : DataStreamTest::moveFromOwnedDataStream()
PASS : DataStreamTest::assignsFromOwnedQDataStream()
PASS : DataStreamTest::assignsFromOwnedDataStream()
PASS : DataStreamTest::returnsFromOwnedQDataStream()
PASS : DataStreamTest::returnsFromOwnedDataStream()
The test suite follows. The binary compatibility test is extensive and all but excludes the possibility that the UB we depend on is problematic. Note that QDataStream's layout cannot change within the major Qt version - so the above code will work on all future Qt 5 versions.
#include <QtTest>
class DataStreamTest : public QObject {
Q_OBJECT
static QObjectData *getD(QObject *obj) {
return static_cast<DataStreamTest *>(obj)->d_ptr.data();
}
static bool wasDeleted(QObject *obj) { return getD(obj)->wasDeleted; }
template <typename T, typename... Args>
DataStream make_stream(Args &&... args) {
return T(std::forward<Args>(args)...);
}
static QDataStream::ByteOrder flipped(QDataStream::ByteOrder o) {
return (o == QDataStream::BigEndian) ? QDataStream::LittleEndian
: QDataStream::BigEndian;
}
Q_SLOT void isBinaryCompatible() {
QCOMPARE(sizeof(DataStream), sizeof(QDataStream));
QCOMPARE(sizeof(DataStream::Proxy), sizeof(QDataStream));
struct Test {
QByteArray data;
QDataStream ds{&data, QIODevice::ReadWrite};
void check(int loc = 0) {
if (!loc) {
check(1);
ds.setDevice(nullptr);
check(1);
}
QCOMPARE(!!ds.device(), DataStream::ownsDevice(&ds));
QCOMPARE(ds.device(), DataStream::p(&ds)->dev);
if (!loc) check(2);
bool noswap = DataStream::p(&ds)->noswap;
QCOMPARE(noswap, DataStream::p(&ds)->noswap);
QCOMPARE(ds.byteOrder(), DataStream::p(&ds)->byteorder);
if (loc != 2) {
ds.setByteOrder(flipped(ds.byteOrder()));
noswap = !noswap;
}
if (!loc) check(2);
QCOMPARE(noswap, DataStream::p(&ds)->noswap);
if (!loc) check(3);
QCOMPARE(ds.version(), DataStream::p(&ds)->ver);
if (loc != 3) ds.setVersion(QDataStream::Qt_4_0);
if (!loc) check(3);
if (!loc) check(4);
QCOMPARE(ds.status(), DataStream::p(&ds)->q_status);
if (loc != 4) ds.setStatus(QDataStream::ReadPastEnd);
if (!loc) check(4);
}
} test;
test.check();
}
Q_SLOT void streams() {
QString str{"Hello, world"};
QVector<uint> ints{44, 0xDEADBEEF, 1};
QByteArray data;
DataStream ds(&data, QIODevice::ReadWrite);
ds << str << ints;
ds.device()->reset();
QString str2;
QVector<uint> ints2;
ds >> str2 >> ints2;
QCOMPARE(str2, str);
QCOMPARE(ints2, ints);
}
Q_SLOT void movesFromNotOwnedQDataStream() {
QBuffer buf;
QDataStream ds(&buf);
QVERIFY(ds.device() == &buf);
DataStream ds2(std::move(ds));
QVERIFY(!ds.device());
QVERIFY(ds2.device() == &buf);
QVERIFY(!wasDeleted(&buf));
}
Q_SLOT void movesFromNotOwnedDataStream() {
QBuffer buf;
DataStream ds(&buf);
QVERIFY(ds.device() == &buf);
DataStream ds2(std::move(ds));
QVERIFY(!ds.device());
QVERIFY(ds2.device() == &buf);
QVERIFY(!wasDeleted(&buf));
}
Q_SLOT void assignsFromNotOwnedQDataStream() {
QBuffer buf;
QDataStream ds(&buf);
QVERIFY(ds.device() == &buf);
DataStream ds2;
ds2 = std::move(ds);
QVERIFY(!ds.device());
QVERIFY(ds2.device() == &buf);
QVERIFY(!wasDeleted(&buf));
}
Q_SLOT void assignsFromNotOwnedDataStream() {
QBuffer buf;
DataStream ds(&buf);
QVERIFY(ds.device() == &buf);
DataStream ds2;
ds2 = std::move(ds);
QVERIFY(!ds.device());
QVERIFY(ds2.device() == &buf);
QVERIFY(!wasDeleted(&buf));
}
Q_SLOT void returnsFromNotOwnedQDataStream() {
QBuffer buf;
{
auto ds = make_stream<QDataStream>(&buf);
QVERIFY(ds.device());
QVERIFY(!ds.ownsDevice());
}
QVERIFY(!wasDeleted(&buf));
}
Q_SLOT void returnsFromNotOwnedDataStream() {
QBuffer buf;
buf.open(QIODevice::ReadWrite);
{
auto ds = make_stream<DataStream>(&buf);
QVERIFY(ds.device());
QVERIFY(!ds.ownsDevice());
}
QVERIFY(!wasDeleted(&buf));
}
Q_SLOT void movesFromOwnedQDataStream() {
QPointer<QIODevice> buf;
{
QByteArray data;
QDataStream ds(&data, QIODevice::ReadWrite);
QVERIFY(DataStream::ownsDevice(&ds));
buf = ds.device();
DataStream ds2(std::move(ds));
QVERIFY(!ds.device());
QVERIFY(ds2.device() == buf);
QVERIFY(buf);
}
QVERIFY(!buf);
}
Q_SLOT void moveFromOwnedDataStream() {
QPointer<QBuffer> buf(new QBuffer);
{
DataStream ds;
ds.setOwnedDevice(buf);
QVERIFY(ds.device() == buf);
DataStream ds2(std::move(ds));
QVERIFY(!ds.device());
QVERIFY(ds2.device() == buf);
QVERIFY(buf);
}
QVERIFY(!buf);
}
Q_SLOT void assignsFromOwnedQDataStream() {
QPointer<QIODevice> buf;
{
QByteArray data;
QDataStream ds(&data, QIODevice::ReadWrite);
QVERIFY(DataStream::ownsDevice(&ds));
buf = ds.device();
DataStream ds2;
ds2 = std::move(ds);
QVERIFY(!ds.device());
QVERIFY(ds2.device() == buf);
QVERIFY(buf);
}
QVERIFY(!buf);
}
Q_SLOT void assignsFromOwnedDataStream() {
QPointer<QBuffer> buf(new QBuffer);
{
DataStream ds;
ds.setOwnedDevice(buf);
QVERIFY(ds.device() == buf);
DataStream ds2;
ds2 = std::move(ds);
QVERIFY(!ds.device());
QVERIFY(ds2.device() == buf);
QVERIFY(buf);
}
QVERIFY(!buf);
}
Q_SLOT void returnsFromOwnedQDataStream() {
QPointer<QIODevice> dev;
QByteArray data;
{
auto ds = make_stream<QDataStream>(&data, QIODevice::ReadWrite);
dev = ds.device();
QVERIFY(ds.device());
QVERIFY(ds.ownsDevice());
}
QVERIFY(!dev);
}
Q_SLOT void returnsFromOwnedDataStream() {
QPointer<QIODevice> dev;
QByteArray data;
{
auto ds = make_stream<DataStream>(&data, QIODevice::ReadWrite);
dev = ds.device();
QVERIFY(ds.device());
QVERIFY(ds.ownsDevice());
}
QVERIFY(!dev);
}
};
QTEST_MAIN(DataStreamTest)
#include "main.moc"