[Solved] Basic QIODevice subclass in Qt4



  • Hi
    I'm trying, and have been for the whole day with no luck, to subclass a QIODevice to encrypt/dycrypt a file. Something similar to the example valid for Qt3 : "Writing a Custom IODevice":http://doc.qt.nokia.com/qq/qq12-iodevice.html

    I have unsucessfully tried to port the code from Qt3 to Qt4.

    I have made the most basic subclass and get the following errors:

    bq. In member function 'virtual qint64 scIODev::readData(char*, qint64)':
    .../src/corelib/io/qiodevice.h:155: error:
    'virtual qint64 QIODevice::readData(char*, qint64)' is protected
    scIODev.cpp:11: error: within this context
    In member function 'virtual qint64 scIODev::writeData(const char*, qint64)':
    .../src/corelib/io/qiodevice.h:157: error:
    'virtual qint64 QIODevice::writeData(const char*, qint64)' is protected
    scIODev.cpp:17: error: within this context

    This is my header file: scIODev.h

    @
    #ifndef SCIODEV_H
    #define SCIODEV_H

    #include <QIODevice>

    class scIODev: public QIODevice
    {
    Q_OBJECT
    public:
    scIODev(QIODevice *parent);
    private:
    QIODevice *pIODev;
    protected:
    qint64 readData(char *data, qint64 maxSize);
    qint64 writeData(const char *data, qint64 maxSize);
    };
    #endif
    @

    This is my source file: scIODev.cpp

    @
    #include "scIODev.h"

    scIODev::scIODev(QIODevice *parent):
    QIODevice(parent)
    {
    pIODev = parent;
    }

    qint64 scIODev::readData(char *data, qint64 maxSize)
    {
    qint64 bytesRead = pIODev -> readData(data, maxSize); //Line 11
    return bytesRead;
    }

    qint64 scIODev::writeData(const char* data, qint64 maxSize)
    {
    qint64 i = pIODev -> writeData(data, maxSize); //Line 17
    return i;
    }
    @

    I am working on Windows XP in a MinGW/qt environment with "Qt SDK by Nokia v2010.05"

    Thanks for any contributions

    Edit: due to the general nature of the problem, moved to C++ gurus for now; Andre



  • This is a basic C++ issue.
    The problem is, is that you are in fact trying to access protected methods in another class. The IODevice you should be operating on, is this, not a "parent" QIODevice. I'm not sure why you gave your scIODev a QIODevice as a parent. The standard QIODevice as a QObject* as a parent, and so should your device, I think.



  • Hi,

    the base class in general is ok, as it is used as QIODevice (e.g. for a QTextStream).
    The problem is that you try to access a protected function of another object, which is not allowed in C++, unless you are of the same class. You are derived.

    What you could do is derive from QFile (so you are an IODevice) and overwrite these methods and call the base methods. The you can decrypt and call methods of the base class. Did you try this out?

    or you call

    • qint64 QIODevice::read ( char * data, qint64 maxSize )
    • qint64 QIODevice::write ( const char * data, qint64 maxSize )

    which are public functions.



  • Yup you are right, this is a basic C++ issue and my C++ is a bit rusty.
    Thanks for moving the post to the correct group. I've been checking up on the basics a bit.

    qiodevice.h implements the virtual function as pure with no body, but the idea is to accept other IODevices as parents, such as QFile, and use their readData or writeData functions after a bit a preprocessing by my subclass scIODev.

    so to explicitly access the parent function readData I have corrected the lines to
    @
    qint64 bytesRead = QIODevice::readData(data, maxSize);
    @
    @
    qint64 i = QIODevice::writeData(data, maxSize);
    @

    This compiles but then doesn't manage to link properly I get undefined references at the calls of QIODevice::readData and QIODevice::writeData.

    The constructor has been corrected to the standard parent of a QIODevice
    @
    scIODev::scIODev(QObject *parent):
    QIODevice(parent)
    {
    }
    @

    Andre: using this-> just recursively calls its own function.. I want to use the inherited virtual function.

    my main.cpp file is as the following:
    @
    #include <QFile>
    #include <QTextStream>
    #include <QDebug>
    #include "scIODev.h"

    int main()
    {
    QFile file("output.dat");
    scIODev dv(&file);
    dv.open(QIODevice::WriteOnly);
    QTextStream ts(&dv);
    ts << "Hello ";
    dv.close();
    return 0;
    }
    @

    Gerolf: Thanks for the response. Deriving from QFile and adding functions will probably be what I'll end up doing when I eventually give up, since I'm not progressing much here.. and yet it seemed as a simple thing, at first.. Will need to do some catching up on polymorphism and inheritance.



  • If you've read my post completly, you could also do:

    @
    #include "scIODev.h"

    scIODev::scIODev(QIODevice *parent):
    QIODevice(parent)
    {
    pIODev = parent;
    }

    qint64 scIODev::readData(char *data, qint64 maxSize)
    {
    qint64 bytesRead = pIODev -> read (data, maxSize); //Line 11
    return bytesRead;
    }

    qint64 scIODev::writeData(const char* data, qint64 maxSize)
    {
    qint64 i = pIODev -> write(data, maxSize); //Line 17
    return i;
    }
    @

    Just calling

    @
    qint64 bytesRead = QIODevice::readData(data, maxSize);
    @

    must result in a linker error, as you call your pure virtual base functions, which does not exist! You must call on pIODev!



  • Man you guys are quick :) , greatly apreciated!

    Gerolf: Tried it and several other combinations, still have the protected error.

    So I have the protected function problem..

    OK, so how do I access the inherited virtual function, if its in the protected domain.

    @
    QFile file("output.dat");
    scIODev dv(&file);
    dv.writeData( &data, len ) // <-- This should first access my function
    //Then I want to pass on the processed data to writeData() implemented by QFile
    @

    Something is wrong with my structure then.

    I have meerly tried to copy and customize the ""Writing a Custom IODevice":http://doc.qt.nokia.com/qq/qq12-iodevice.html" Example.

    Why are the pure virtual functions in QIODevice protected ?



  • What you seem to miss is how you call functions from a baseclass, and you seem to confuse the notions of a base class and a parent. A parent object is something used throughout Qt to maintain parent/child relationships, automatic destruction of children, and (for widgets) control rendering and if a widget is going to be a top level window or not. A baseclass on the other hand is the class that is specialized by your class. It is an is-a relationship, while the parent-child relationship is a has-a relationship.

    If you want to call functions from a baseclass, you do something this:
    @
    QIODevice::readData();
    @

    However, those functions are pure virtual for QIODevice, so calling them isn't going to work. You will have to implement them for your class using the public API of whatever classes you are using in your implementation. Calling them does make sense if you are not subclassing QIODevice, but a more specialized subclass of QIODevice that already has those implemented as something sane (such as QFile, as suggested before).

    The problem is, I think, that you seem to want your class to work with any QIODevice as a "parent", as as to chain them. Did I get that correctly? If so, then perhaps "this class":http://libqxt.bitbucket.org/doc/0.6/qxtpipe.html can either function as a base class or as inspiration for you.



  • Hi paucoma,

    how do you get a protected error for a public function?
    read and write are public methods.

    only readData and writeData are protected.



  • [quote]
    how do you get a protected error for a public function?
    read and write are public methods.
    [/quote]

    Gerolf: ooops, I read that code too quickly... comes from looking at the same (similar) code too many times. Sorry, you are right. That solution I will have to look into... That was actually in the previous post too.. sorry for not paying attention.. Since in the Documentation it says only readData and writeData need to be reimplemented I had that idea quite fixed in my head and wasn't thinking about the other functions.

    bq. The problem is, I think, that you seem to want your class to work with any QIODevice as a “parent”, as as to chain them. Did I get that correctly?

    Correct!, I should have expressed that desire earlier.. I thought that too much information at once would confuse. but yes, I would like it to work on multiple classes which derive from QIODevice such as QBuffer and QFile.

    I will have a read, thanks.

    Thanks both very much for the quick and helpful responses!

    Ultimately I will probably inherit QFile for the moment and use base class calls, untill the challenge to extend to other IODevices comes.



  • If you read the docs for IODevices, it states you must implement readData and writeData, that' scorrect. but it didnÄ't say you have to call them! the logic is up to youm, and if you sue another device, use it's public interface.



  • Hi,

    I created a wiki page as porting of the QQ 12 article to Qt 4:

    "wiki":http://developer.qt.nokia.com/wiki/CustomIoDevice

    the main code is:

    @
    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;
    

    }

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



  • Thanks Gerolf, Great iniciative in porting the code!

    So if I understand correctly,

    -- readData() and writeData(...) are not part of the general class Interface and therefore I can be pretty sure that "normally" it won't be called on its own, but only indirectly through read().

    -- If I subclass QIODevice I should keep protected the reimplementations of readData(...) and writeData(...) to ensure this.



  • you are right, yes



  • Hi,

    due to some bug, I updated the Wiki page.
    The constructor of the io device was lacking a QObject* parent object pointer. The example had a mistake, as the encrypted data should never be interpreted as string...

    it is now shown as hex code.



  • Yea, the bad habit, or lets call it lazyness, of interpreting Bytes as string is just the quickest way to output "insignificant" data when you want to see that something is there or something is changing.

    On the other hand looking at the class declaration:
    @
    class CryptDevice : public QIODevice
    {
    public:
    explicit CryptDevice(QIODevice* deviceToUse, QObject* parent);
    ...
    @

    is this necessary because you have no other constructor that accepts a QObject ?

    could it be broken down to
    @
    class CryptDevice : public QIODevice
    {
    public:
    CryptDevice(QObject* parent);
    explicit CryptDevice(QIODevice* deviceToUse);
    ...
    @

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

    On a side note: What about the Q_OBJECT macro? I understand that this needs to be included when you want to use signals and slots mechanism.

    Thanks for the update.



  • Hi Paucoma,

    Q_OBJECT macro is needed, if this class uses signal slot, but my implementation has not signal/slot, no properties. So only the base classes have signal/slot and those have the macros, that's fine. But I add it, for completeness.

    I removed the explicit for the constructor, and set the parent to 0. Two constructors makes no sense, as this device always needs an underlying device. The class en/decrypts data and stores it in the underlying device. Sure you can argue, otherwise you can open/close the device, change the underlying device by a method and open again, yes, but it's a code snippet, a description on how to implement a custom IO device.



  • Note that the Q_OBJECT macro has more uses than just signal/slot. It also is needed for introspection and things like qobject_cast<>(). That may or may not be nessecairy, but I think it is good practice to include Q_OBJECT by default for QObject derived classes.



  • Hi Gerolf!

    Even though the custom IODevice, CryptDevice, does not implement signals and slots itself, it is a class derived from QIODevice which does provide signals, such as:

    • void aboutToClose ()
    • void bytesWritten ( qint64 bytes )
    • void readChannelFinished ()
    • void readyRead ()

    To be able to use these signals from a CryptDevice Object is Q_OBJECT necessary in the definition of CryptDevice? or since QIODevice already declares it, it is not needed.

    Your right, it doesn't make much sense to provide a seperate constructor.

    I have been reading a bit on the explicit keyword and believe I understand that:

    removing the explicit would now allow you to do
    @
    QBuffer bufferUsedLikeAFile(&dataArray);
    SimpleCryptDevice deviceFilter = &bufferUsedLikeAFile;
    @
    before, with the explicit keyword, it would have thrown a compile error.



  • [quote author="paucoma" date="1300957763"]Even though the custom IODevice, CryptDevice, does not implement signals and slots itself, it is a class derived from QIODevice which does provide signals, such as:

    • void aboutToClose ()
    • void bytesWritten ( qint64 bytes )
    • void readChannelFinished ()
    • void readyRead ()

    To be able to use these signals from a CryptDevice Object is Q_OBJECT necessary in the definition of CryptDevice? or since QIODevice already declares it, it is not needed.
    [/quote]

    It is not needed for signals and slots provided by a base class. There are, however, other reasons why you might want to include Q_OBJECT.



  • Thanks Andre for the clarification.



  • bq. To be able to use these signals from a CryptDevice Object is Q_OBJECT necessary in the definition of CryptDevice? or since QIODevice already declares it, it is not needed.

    You can use signals and slots from base classes without the Q_OBJECT macro in CryptDevice. But as Andre mentions, there is more (like qobject_cast) that also relies on the meta object system. so now it is added.

    @
    QBuffer bufferUsedLikeAFile(&dataArray);
    SimpleCryptDevice deviceFilter = &bufferUsedLikeAFile;
    @

    This should not be possible, as QObject assignement is a bad idea. So I also added QBuffer Q_DISABLE_COPY(CryptDevice) to the class. Now, no assignment or copy constructor is possible.

    Explicit means it can't be used indirectly for conversion. So I reach the same by using Q_DISABLE_COPY. Explicit makes sense only for one parameter constructors, and the c'tor was changed to two parameters, so it made no sense anymore.



  • Quote from one of the articles I read about the explicit keyword:

    "Explicit Constructor in C++ By Mridula":http://www.go4expert.com/forums/showthread.php?t=20756

    bq. But explicit on a constructor with multiple arguments has no effect, since such constructors cannot take part in implicit conversions. However, explicit will have an effect if a constructor has multiple arguments and all but one of the arguments has a default value.

    Which would be the case since the QObject defaults to 0, right?



  • But it had 2 parameters without default:

    @
    explicit CryptDevice(QIODevice* deviceToUse, QObject* parent);
    @



  • Normally in Qt, the parent parameter actually defaults 0. Perhaps that should be the case here too:

    @
    CryptDevice(QIODevice* deviceToUse, QObject* parent = 0);
    @

    However, even then, I think explicit is not needed. What would potentially be cast to QIODevice* that would not be a valid argument?



  • That's the difference in time :-)

    it already is, but the explicit was before the code of the c'tor was changed, and there the C'tor did not default it (in fact between I had 2 c'tors, that's why it couldn't default).

    Now we only have one c'tor which has a parent by default 0.

    code from above:

    @
    QBuffer bufferUsedLikeAFile(&dataArray);
    SimpleCryptDevice deviceFilter = &bufferUsedLikeAFile;
    @

    This code could result in a copy constructor / assignment operator call, depending on the compiler. It might be optimized. Both is not allowed for QObjects.



  • Lets see if I understand then:
    if we were to define the constructor as explicit:
    @
    explicit CryptDevice(QIODevice* deviceToUse, QObject* parent=0);
    @
    @
    QIODevice dev;
    QIODevice pdev;
    CryptDevice cdev_1(&dev); //call to explicit constructor
    CryptDevice cdev_2(pdev); //call to explicit constructor
    CryptDevice cdev_3(dev); //illegal! -> compiler error (expecting QIODevice
    )
    CryptDevice cdev_4(cdev_1); //call to compiler generated copy constructor
    CryptDevice cdev_5 = cdev_1; //call to compiler generated copy constructor
    CryptDevice cdev_6 = &dev; //illegal -> compiler error (expecting CryptDevice)
    @

    [quote] Q_DISABLE_COPY(CryptDevice) to the class. Now, no assignment or copy constructor is possible.[/quote]

    @
    Q_DISABLE_COPY(CryptDevice)
    CryptDevice(QIODevice* deviceToUse, QObject* parent=0);
    @
    @
    QIODevice dev;
    QIODevice *pdev;
    CryptDevice cdev_1(&dev); //call to constructor
    CryptDevice cdev_2(pdev); //call to constructor
    CryptDevice cdev_3(dev); // will it try type conversion?
    CryptDevice cdev_4(cdev_1); //illegal -> Copy disabled
    CryptDevice cdev_5 = cdev_1; //illegal -> operator= disabled
    CryptDevice cdev_6 = &dev; //illegal -> operator= disabled
    @

    Or have I just gotten completly lost...



  • Hi Pau,

    I copied your entry and fixed the comments:

    if we were to define the constructor as explicit:
    @
    explicit CryptDevice(QIODevice* deviceToUse, QObject* parent=0);
    @
    @
    QIODevice dev;
    QIODevice *pdev;

    CryptDevice cdev_1(&dev); //call to explicit constructor
    CryptDevice cdev_2(pdev); //call to explicit constructor
    CryptDevice cdev_3(dev); //illegal! -> compiler error (expecting QIODevice*)
    CryptDevice cdev_4(cdev_1); //call to compiler generated copy constructor --> will not work, as QIODevice has Q_DISABLE_COPY
    CryptDevice cdev_5 = cdev_1; //call to compiler generated copy constructor --> will not work, as QIODevice has Q_DISABLE_COPY
    CryptDevice cdev_6 = &dev; //illegal -> compiler error (expecting CryptDevice)
    @

    [quote] Q_DISABLE_COPY(CryptDevice) to the class. Now, no assignment or copy constructor is possible.[/quote]

    @
    Q_DISABLE_COPY(CryptDevice)
    CryptDevice(QIODevice* deviceToUse, QObject* parent=0);
    @
    @
    QIODevice dev;
    QIODevice pdev;
    CryptDevice cdev_1(&dev); //call to constructor
    CryptDevice cdev_2(pdev); //call to constructor
    CryptDevice cdev_3(dev); // illegal! -> compiler error (expecting QIODevice
    )
    CryptDevice cdev_4(cdev_1); //illegal -> Copy disabled
    CryptDevice cdev_5 = cdev_1; //illegal -> operator= disabled
    CryptDevice cdev_6 = &dev; //illegal -> operator= disabled
    @

    Or have I just gotten completly lost...



  • Hi Gerolf: thanks for fixing the comments.
    In the first section I am suposing we dont declare the macro Q_DISABLE_COPY(CryptDevice)

    • In that case, will the compiler automatically generate a copy constructor?
      ** If so, are the following legal?
      *** CryptDevice cdev_4(cdev_1);
      *** CryptDevice cdev_5 = cdev_1;


  • it will try to create it, but it will automatically call the base copy c'tor, which is private --> compiler error



  • Thanks for that clarification, when I try these things I don't know about, I get errors, and it is sometimes hard to understand what the error means, you guys here are really helping me understand whats happening, I'm greatfull! :)

    P.S. I need to read more carefully, sorry.. you did do the correct assumption in your first posting.
    [quote] will not work, as QIODevice has Q_DISABLE_COPY [/quote]



  • Just a short note: It may be handy to provide some custom signal encryptedBytesWritten() just like signals in "QSslSocket:: encryptedBytesWritten()":http://doc.qt.nokia.com/4.7/qsslsocket.html#encryptedBytesWritten. This counts the bytes that were actually written to the underlying device (it might differ from the bytes that went in!).



  • Not with this implementation it doesn't ;-)



  • And with this implementation, when write returns, the bytes are written :-)
    But I should look into this in the my SimpleCryptDevice impl :-) Thanks.



  • Attempting to find out where the bytes are being emited in the case of QFile for example.
    I have done a search in QFile.cpp, QIODevice.cpp and even QObject.cpp and none of them actually has the words "emit bytesWritten"

    I have found: void bytesWritten(qint64 bytes); in qiodevice.h
    Under a signal declaration header macro Q_SIGNALS which I have read to be for "Using Qt with 3rd Party Signals and Slots mechanisms":http://doc.qt.nokia.com/latest/signalsandslots.html#3rd-party-signals-and-slots

    And then I searched for the Q_EMIT macro with no findings (except for a comment in qobject) and I have no further ideas.

    Any suggestions on where to find out where this signal is being emited?

    p.s. This is just a simple curiosity I had, if someone knows it off the top of their head it would be nice to know, otherwise I wouldn't want anybody to waste their time trying to answer the question.

    p.p.s. Gerolf: Just noticed your "quoted" signature, was laughing for a good long minute and the smile will probably still be on my face when I wake up tomorrow. :D



  • QIODevice is a base class. It defines the interface.

    If you look at the docs of QFile, "it's stated":http://doc.qt.nokia.com/latest/qfile.html#signals :

    bq. Unlike other QIODevice implementations, such as QTcpSocket, QFile does not emit the aboutToClose(), bytesWritten(), or readyRead() signals. This implementation detail means that QFile is not suitable for reading and writing certain types of files, such as device files on Unix platforms.

    qbuffer.cpp emits it on line 82.

    qprocess.cpp emits it on line 980.

    qwindowspipewriter.cpp emits it on line 163.

    For all sockets, I did not check now.



  • Hehe, thanks Gerolf. Man, what bad luck having chosen QFile for a "random" QIODevice to see where it would emit the signal.



  • [quote author="Volker" date="1300991199"]Just a short note: It may be handy to provide some custom signal encryptedBytesWritten() just like signals in "QSslSocket:: encryptedBytesWritten()":http://doc.qt.nokia.com/4.7/qsslsocket.html#encryptedBytesWritten. This counts the bytes that were actually written to the underlying device (it might differ from the bytes that went in!).[/quote]

    Wouldn't that be overkill, because you already have access to the underlying QIODevice directly? If you're interested in what was written, couldn't simply connect to that IODevices' bytesWritten() signal?



  • [quote author="Andre" date="1301002233"]
    Wouldn't that be overkill, because you already have access to the underlying QIODevice directly? If you're interested in what was written, couldn't simply connect to that IODevices' bytesWritten() signal?[/quote]

    Only if it's not a QFile!

    bq. Unlike other QIODevice implementations, such as QTcpSocket, QFile does not emit the aboutToClose(), bytesWritten(), or readyRead() signals. This implementation detail means that QFile is not suitable for reading and writing certain types of files, such as device files on Unix platforms.



  • [quote author="Andre" date="1301002233"]
    [quote author="Volker" date="1300991199"]Just a short note: It may be handy to provide some custom signal encryptedBytesWritten() just like signals in "QSslSocket:: encryptedBytesWritten()":http://doc.qt.nokia.com/4.7/qsslsocket.html#encryptedBytesWritten. This counts the bytes that were actually written to the underlying device (it might differ from the bytes that went in!).[/quote]

    Wouldn't that be overkill, because you already have access to the underlying QIODevice directly? If you're interested in what was written, couldn't simply connect to that IODevices' bytesWritten() signal?[/quote]

    In this case yes, you're right. I had that QSslSocket in mind, but you do not have access to the "regular" TCP socket in that case.



  • There is a more enhanced eexample, also in the wiki now: "Simple Crypt IO Device":http://developer.qt.nokia.com/wiki/Simple_Crypt_IO_Device . It uses the "SimpleCrypt class":http://developer.qt.nokia.com/wiki/Simple_encryption to encrypt the data and stores it via a custom IO device in any other device.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.