Custom IO Device: Difference between revisions

From Qt Wiki
Jump to navigation Jump to search
No edit summary
 
No edit summary
 
(6 intermediate revisions by 2 users not shown)
Line 1: Line 1:
=Writing a Custom I/O Device=


This is a port of the article in [http://doc.qt.nokia.com/qq/qq12-iodevice.html Qt Quarterly 12 about writing a custom <span class="caps">QIOD</span>evice] ''[doc.qt.nokia.com]''


* Note: a more complex example can be found here [[Simple Crypt IO Device|Simple_Crypt_IO_Device]]
[[Category:Snippets]]


==Usage:==
'''English''' | [[Custom_IO_Device_German|German]]
 
 
 
= Writing a Custom I/O Device =
 
This is a port of the article in [http://doc.qt.io/qt-5/qiodevice.html Qt Quarterly 12 about writing a custom QIODevice]
 
* Note: a more complex example can be found here [[Simple_Crypt_IO_Device]]
 
== 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:
The following code snippet shows how we would use the custom I/O device to encrypt data and store the result in a file:


And on the possible usage (in our example code in git [http://www.gitorious.org/qtdevnet-wiki-mvc/qtdevnet-custom-iodevice qtdevnet-wiki-mvc/qtdevnet-custom-iodevice] ''[gitorious.org]'')
<code>
QFile file("output.dat");
CryptDevice cryptDevice(&file)
QTextStream out(&cryptDevice);
cryptDevice.open(QIODevice::WriteOnly);
out << "Hello World";
</code>
 
And on the possible usage (in our example code in git [http://code.qt.io/cgit/%7bnon-gerrit%7d/qt-labs/devnet-examples.git/tree/])
 
 
=== Encryption ===
 
<code>
QByteArray dataArray;


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


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


Example image of the test app:
Example image of the test app:


[[Image:CustomIoDevice.jpg|Test app]]
https://lh3.googleusercontent.com/_m1PNLlZctqY/TYpctkjGvTI/AAAAAAAAAFI/BjZnvJhatRY/s800/CustomIoDevice.jpg


==The Custom I/O Device==
== The Custom I/O Device ==


Writing a custom <span class="caps">QIOD</span>evice class in Qt 4 involves inheriting <span class="caps">QIOD</span>evice and then reimplementing a set of virtual functions.
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:
There is a big difference regarding writing a custom IO device compared to Qt 3: you only have to rewrite 2 functions:


* qint64 <span class="caps">QIOD</span>evice::readData ( char * data, qint64 maxSize )
* qint64 QIODevice::readData ( char * data, qint64 maxSize )
* qint64 <span class="caps">QIOD</span>evice::writeData ( const 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 <span class="caps">QIOD</span>evice.
Our CryptDevice class will be a sequential I/O device. Whether it's synchronous or asynchronous depends on the underlying QIODevice.


==Source Code==
== Source Code ==


The class definition looks like this:
The class definition looks like this:
<code>
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)
};
</code>


The constructor definition is pretty straightforward
The constructor definition is pretty straightforward


As our device should be sequential, we re-implement isSequential<br />
<code>
CryptDevice::CryptDevice(QIODevice* deviceToUse, QObject* parent) :
QIODevice(parent),
underlyingDevice(deviceToUse)
{
}
</code>


In <code>open()</code>, we open the underlying device if it’s not already open and set the device state to ''mode''.
As our device should be sequential, we re-implement isSequential
<code>
bool CryptDevice::isSequential() const
{
return true;
}
</code>
 
In <code>open()</code>, we open the underlying device if it's not already open and set the device state to ''mode''.
 
<code>
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;
}
</code>


Closing is trivial.
Closing is trivial.


When reading a block, we call <code>read()</code> on the underlying device. At the end, we <span class="caps">XOR</span> each byte read from the device with the magic constant 0×5E.
<code>
void CryptDevice::close()
{
underlyingDevice->close();
setOpenMode(NotOpen);
}
</code>
 
When reading a block, we call <code>read()</code> on the underlying device. At the end, we XOR each byte read from the device with the magic constant 0x5E.
 
<code>
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;


When writing a block, we create a temporary buffer with the <span class="caps">XOR</span>’d data. A more efficient implementation would use a 4096-byte buffer on the stack and call <code>write()</code> multiple times if size is larger than the buffer.
return deviceRead;
}
</code>


===Categories:===
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 <code>write()</code> multiple times if size is larger than the buffer.


* [[:Category:snippets|snippets]]
<code>
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);
}
</code>

Latest revision as of 12:10, 30 March 2015


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