Custom IO Device

From Qt Wiki
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.


English | German


Writing a Custom I/O Device

This is a port of the article in Qt Quarterly 12 about writing a custom QIODevice

Usage:

The following code snippet shows how we would use the custom I/O device to encrypt data and store the result in a file:

 QFile file("output.dat");
 CryptDevice cryptDevice(&file)
 QTextStream out(&cryptDevice);
 cryptDevice.open(QIODevice::WriteOnly);
 out << "Hello World";

And on the possible usage (in our example code in git [1])


Encryption

 QByteArray dataArray;

QBuffer bufferUsedLikeAFile(&dataArray);
 CryptDevice deviceFilter(&bufferUsedLikeAFile);
 deviceFilter.open(QIODevice::WriteOnly);
 QTextStream stream(&deviceFilter);
 QString szText = rawText->toPlainText();
 stream << szText;


Decryption

 QBuffer bufferUsedLikeAFile(&dataArray);
 CryptDevice deviceFilter(&bufferUsedLikeAFile);
 deviceFilter.open(QIODevice::ReadOnly);
 QTextStream stream(&deviceFilter);
 QString szText = stream.readAll();
 decryptedText->setPlainText(szText);

Example image of the test app:

CustomIoDevice.jpg

The Custom I/O Device

Writing a custom QIODevice class in Qt 4 involves inheriting QIODevice and then reimplementing a set of virtual functions.

There is a big difference regarding writing a custom IO device compared to Qt 3: you only have to rewrite 2 functions:

  • qint64 QIODevice::readData ( char * data, qint64 maxSize )
  • qint64 QIODevice::writeData ( const char * data, qint64 maxSize )

Our CryptDevice class will be a sequential I/O device. Whether it's synchronous or asynchronous depends on the underlying QIODevice.

Source Code

The class definition looks like this:

class CryptDevice : public QIODevice
{
 Q_OBJECT
public:
 CryptDevice(QIODevice* deviceToUse, QObject* parent = 0);
 bool open(OpenMode mode);
 void close();
 bool isSequential() const;
protected:
 qint64 readData(char* data, qint64 maxSize);
 qint64 writeData(const char* data, qint64 maxSize);
private:
 QIODevice* underlyingDevice;
 Q_DISABLE_COPY(CryptDevice)
};

The constructor definition is pretty straightforward

CryptDevice::CryptDevice(QIODevice* deviceToUse, QObject* parent) :
 QIODevice(parent),
 underlyingDevice(deviceToUse)
{
}

As our device should be sequential, we re-implement isSequential

bool CryptDevice::isSequential() const
{
 return true;
}

In

open()

, we open the underlying device if it's not already open and set the device state to mode.

bool CryptDevice::open(OpenMode mode)
{
 bool underlyingOk;
 if (underlyingDevice->isOpen())
 underlyingOk = (underlyingDevice->openMode() != mode);
 else
 underlyingOk = underlyingDevice->open(mode);

if (underlyingOk)
 {
 setOpenMode(mode);
 return true;
 }
 return false;
}

Closing is trivial.

void CryptDevice::close()
{
 underlyingDevice->close();
 setOpenMode(NotOpen);
}

When reading a block, we call

read()

on the underlying device. At the end, we XOR each byte read from the device with the magic constant 0x5E.

qint64 CryptDevice::readData(char* data, qint64 maxSize)
{
 qint64 deviceRead = underlyingDevice->read(data, maxSize);
 if (deviceRead == 1)
 return -1;
 for (qint64 i = 0; i < deviceRead; +''i)
 data[i] = data[i] ^ 0x5E;

 return deviceRead;
}

When writing a block, we create a temporary buffer with the XOR'd data. A more efficient implementation would use a 4096-byte buffer on the stack and call

write()

multiple times if size is larger than the buffer.

qint64 CryptDevice::writeData(const char* data, qint64 maxSize)
{
 QByteArray buffer((int)maxSize, 0);
 for (int i = 0; i < (int)maxSize;''+i)
 buffer[i] = data[i] ^ 0x5E;
 return underlyingDevice->write(buffer.data(), maxSize);
}