10

Is it possible to use a QFile like a std::iostream? I'm quite sure there must be a wrapper out there. The question is where?

I have another libs, which requires a std::istream as input parameter, but in my program i only have a QFile at this point.

Andreas Roth
  • 699
  • 2
  • 9
  • 30

5 Answers5

7

I came up with my own solution using the following code:

#include <ios>
#include <QIODevice>

class QStdStreamBuf : public std::streambuf
{
public:
    QStdStreamBuf(QIODevice *dev) : std::streambuf(), m_dev(dev)
    {
        // Initialize get pointer.  This should be zero so that underflow is called upon first read.
        this->setg(0, 0, 0);
    }

protected:
virtual std::streamsize xsgetn(std::streambuf::char_type *str, std::streamsize n)
{
    return m_dev->read(str, n);
}

virtual std::streamsize xsputn(const std::streambuf::char_type *str, std::streamsize n)
{
    return m_dev->write(str, n);
}

virtual std::streambuf::pos_type seekoff(std::streambuf::off_type off, std::ios_base::seekdir dir, std::ios_base::openmode /*__mode*/)
{
    switch(dir)
    {
        case std::ios_base::beg:
            break;
        case std::ios_base::end:
            off = m_dev->size() - off;
            break;
        case std::ios_base::cur:
            off = m_dev->pos() + off;
            break;
    }
    if(m_dev->seek(off))
        return m_dev->pos();
    else
        return std::streambuf::pos_type(std::streambuf::off_type(-1));
}
virtual std::streambuf::pos_type seekpos(std::streambuf::pos_type off, std::ios_base::openmode /*__mode*/)
{
    if(m_dev->seek(off))
        return m_dev->pos();
    else
        return std::streambuf::pos_type(std::streambuf::off_type(-1));
}

virtual std::streambuf::int_type underflow()
{ 
    // Read enough bytes to fill the buffer.
    std::streamsize len = sgetn(m_inbuf, sizeof(m_inbuf)/sizeof(m_inbuf[0]));

    // Since the input buffer content is now valid (or is new)
    // the get pointer should be initialized (or reset).
    setg(m_inbuf, m_inbuf, m_inbuf + len);

    // If nothing was read, then the end is here.
    if(len == 0)
        return traits_type::eof();

    // Return the first character.
    return traits_type::not_eof(m_inbuf[0]);
}


private:
    static const std::streamsize BUFFER_SIZE = 1024;
    std::streambuf::char_type m_inbuf[BUFFER_SIZE];
    QIODevice *m_dev;
};

class QStdIStream : public std::istream
{
public:
    QStdIStream(QIODevice *dev) : std::istream(m_buf = new QStdStreamBuf(dev)) {}
    virtual ~QStdIStream()
    {
        rdbuf(0);
        delete m_buf;
    }

private:
    QStdStreamBuf * m_buf;
};

I works fine for reading local files. I haven't tested it for writing files. This code is surely not perfect but it works.

George Hilliard
  • 15,402
  • 9
  • 58
  • 96
Andreas Roth
  • 699
  • 2
  • 9
  • 30
  • 1
    According to [Cppreference](https://en.cppreference.com/w/cpp/io/basic_streambuf/underflow), one should use `Traits::to_int_type()` to convert `std::streambuf::char_type` to `std::streambuf::int_type` in `underflow`. – m7913d Apr 28 '22 at 20:09
5

I came up with my own solution (which uses the same idea Stephen Chu suggested)

#include <iostream>
#include <fstream>
#include <cstdio>

#include <QtCore>

using namespace std;

void externalLibFunction(istream & input_stream) {
    copy(istream_iterator<string>(input_stream),
         istream_iterator<string>(),
         ostream_iterator<string>(cout, " "));
}

ifstream QFileToifstream(QFile & file) {
    Q_ASSERT(file.isReadable());        
    return ifstream(::_fdopen(file.handle(), "r"));
}

int main(int argc, char ** argv)
{
    QFile file("a file");
    file.open(QIODevice::WriteOnly);
    file.write(QString("some string").toLatin1());
    file.close();
    file.open(QIODevice::ReadOnly);
    std::ifstream ifs(QFileToifstream(file));
    externalLibFunction(ifs);
}

Output:

some string

This code uses std::ifstream move constructor (C++x0 feature) specified in 27.9.1.7 basic_ifstream constructors section of Working Draft, Standard for Programming Language C++:

basic_ifstream(basic_ifstream&& rhs);
Effects: Move constructs from the rvalue rhs. This is accomplished by move constructing the base class, and the contained basic_filebuf. Next basic_istream::set_rdbuf(&sb) is called to install the contained basic_filebuf.

See How to return an fstream (C++0x) for discussion on this subject.

Community
  • 1
  • 1
Piotr Dobrogost
  • 41,292
  • 40
  • 236
  • 366
  • 3
    For the line `ifstream(::_fdopen(file.handle(), "r"));` I get: `error: no matching function for call to 'std::basic_ifstream::basic_ifstream(FILE*)' std::ifstream fileInputStream(::_fdopen(testPatternPng.handle(), "r"));` – Troyseph Jun 02 '16 at 08:06
  • `::fdopen` should be available on every platform unlike `_fdopen` which is the MS version – Jean-Michaël Celerier Jun 18 '22 at 21:21
4

If the QFile object you get is not open for read already, you can get filename from it and open an ifstream object.

If it's already open, you can get file handle/descriptor with handle() and go from there. There's no portable way of getting a fstream from platform handle. You will have to find a workaround for your platforms.

Stephen Chu
  • 12,724
  • 1
  • 35
  • 46
  • Just getting the filename will not work for Qt resources. Will the handle() method work if the QFile is pointing on a resource? – galinette Aug 22 '13 at 07:11
  • Actually, you can use Boost.IOStreams for platform-independent way of creating an iostream from fd: First you create a stream buffer: `boost::iostreams::stream_buffer streambuf(handle)`, then pass the pointer to stream buffer to your new `std::iostream(&streambuf)`. – Alexander Shishenko Jan 24 '17 at 10:38
2

Here's a good guide for subclassing std::streambuf to provide a non-seekable read-only std::istream: https://stackoverflow.com/a/14086442/316578

Here is a simple class based on that approach which adapts a QFile into an std::streambuf which can then be wrapped in an std::istream.

#include <iostream>
#include <QFile>

constexpr qint64 ERROR = -1;
constexpr qint64 BUFFER_SIZE = 1024;

class QFileInputStreamBuffer final : public std::streambuf {
private:
    QFile &m_file;
    QByteArray m_buffer;
public:
    explicit QFileInputStreamBuffer(QFile &file)
        : m_file(file),
          m_buffer(BUFFER_SIZE, Qt::Uninitialized) {
    }

    virtual int underflow() override {
        if (atEndOfBuffer()) {
            // try to get more data
            const qint64 bytesReadIntoBuffer = m_file.read(m_buffer.data(), BUFFER_SIZE);
            if (bytesReadIntoBuffer != ERROR) {
                setg(m_buffer.data(), m_buffer.data(), m_buffer.data() + bytesReadIntoBuffer);
            }
        }
        if (atEndOfBuffer()) {
            // no more data available
            return std::char_traits<char>::eof();
        }
        else {
            return std::char_traits<char>::to_int_type(*gptr());
        }
    }

private:
    bool atEndOfBuffer() const {
        return gptr() == egptr();
    }
};

If you want to be able to more things like seek, write, etc., then you'd need one of the other more complex solutions here which override more streambuf functions.

Anthony Hayward
  • 2,164
  • 21
  • 17
0

If you don't care much for performance you can always read everything from the file and dump it into an std::stringstream and then pass that to your library. (or the otherway, buffer everything to a stringstream and then write to a QFile)

Other than that, it doesn't look like the two can inter-operate. At any rate, Qt to STL inter operations are often a cause for obscure bugs and subtle inconsistencies if the version of STL that Qt was compiled with is different in any way from the version of STL you are using. This can happen for instance if you change the version of Visual Studio.

rubenvb
  • 74,642
  • 33
  • 187
  • 332
shoosh
  • 76,898
  • 55
  • 205
  • 325
  • 1
    Thanks for your suggestion, but stringstream isn't an option because the file can become quite big. So reading all the data into memory is quite ugly. – Andreas Roth Mar 05 '11 at 14:36