Search code examples
windowsqtconsolecarriage-returnlinefeed

Read binary data from QProcess in Windows


I have some .exe file (say some.exe) that writes to the standard output binary data. I have no sources of this program. I need to run some.exe from my C++/Qt application and read standard output of the process I created. When I'm trying to do this with QProcess::readAll someone replaces byte \n (0x0d) to \r\n (0x0a 0x0d).

Here is a code:

QProcess some;
some.start( "some.exe", QStringList() << "-I" << "inp.txt" );
// some.setTextModeEnabled( false ); // no effect at all
some.waitForFinished();
QByteArray output = some.readAll();

I tried in cmd.exe to redirect output to file like this:

some.exe -I inp.txt > out.bin

and viewed out.bin with hexedit there was 0a 0d in the place where should be 0d.

Edit:
Here is a simple program to emulate some.exe behaviour:

#include <stdio.h>
int main() {
    char buf[] = { 0x00, 0x11, 0x0a, 0x33 };
    fwrite( buf, sizeof( buf[ 0 ] ), sizeof( buf ), stdout );
}

run:

a.exe > out.bin

//out.bin
00 11 0d 0a 33

Note, that I can't modify some.exe that's why I shouldn't modify my example like _setmode( _fileno( stdout, BINARY ) )

The question is: how can I say to QProcess or to Windows or to console do not change CR with LF CR?

OS: Windows 7
Qt: 5.6.2


Solution

  • how can I say to QProcess or to Windows or to console do not change CR with LF CR?

    They don't change anything. some.exe is broken. That's all. It outputs the wrong thing. Whoever made it output brinary data in text mode has messed up badly.

    There's a way to recover, though. You have to implement a decoder that will fix the broken output of some.exe. You know that every 0a has to be preceded by 0d. So you have to parse the output, and if you find a 0a, and there's 0d before it, remove the 0d, and continue. Optionally, you can abort if a 0a is not preceded by 0d - some.exe should not produce such output since it's broken.

    The appendBinFix function takes the corrupted data and appends the fixed version to a buffer.

    // https://github.com/KubaO/stackoverflown/tree/master/questions/process-fix-binary-crlf-51519654
    #include <QtCore>
    #include <algorithm>
    
    bool appendBinFix(QByteArray &buf, const char *src, int size) {
       bool okData = true;
       if (!size) return okData;
       constexpr char CR = '\x0d';
       constexpr char LF = '\x0a';
       bool hasCR = buf.endsWith(CR);
       buf.resize(buf.size() + size);
       char *dst = buf.end() - size;
       const char *lastSrc = src;
       for (const char *const end = src + size; src != end; src++) {
          char const c = *src;
          if (c == LF) {
             if (hasCR) {
                std::copy(lastSrc, src, dst);
                dst += (src - lastSrc);
                dst[-1] = LF;
                lastSrc = src + 1;
             } else
                okData = false;
          }
          hasCR = (c == CR);
       }
       dst = std::copy(lastSrc, src, dst);
       buf.resize(dst - buf.constData());
       return okData;
    }
    
    bool appendBinFix(QByteArray &buf, const QByteArray &src) {
       return appendBinFix(buf, src.data(), src.size());
    }
    

    The following test harness ensures that it does the right thing, including emulating the output of some.exe (itself):

    #include <QtTest>
    #include <cstdio>
    #ifdef Q_OS_WIN
    #include <fcntl.h>
    #include <io.h>
    #endif
    
    const auto dataFixed = QByteArrayLiteral("\x00\x11\x0d\x0a\x33");
    const auto data = QByteArrayLiteral("\x00\x11\x0d\x0d\x0a\x33");
    
    int writeOutput() {
    #ifdef Q_OS_WIN
       _setmode(_fileno(stdout), _O_BINARY);
    #endif
       auto size = fwrite(data.data(), 1, data.size(), stdout);
       qDebug() << size << data.size();
       return (size == data.size()) ? 0 : 1;
    }
    
    class AppendTest : public QObject {
       Q_OBJECT
       struct Result {
          QByteArray d;
          bool ok;
          bool operator==(const Result &o) const { return ok == o.ok && d == o.d; }
       };
       static Result getFixed(const QByteArray &src, int split) {
          Result f;
          f.ok = appendBinFix(f.d, src.data(), split);
          f.ok = appendBinFix(f.d, src.data() + split, src.size() - split) && f.ok;
          return f;
       }
       Q_SLOT void worksWithLFCR() {
          const auto lf_cr = QByteArrayLiteral("\x00\x11\x0a\x0d\x33");
          for (int i = 0; i < lf_cr.size(); ++i)
             QCOMPARE(getFixed(lf_cr, i), (Result{lf_cr, false}));
       }
       Q_SLOT void worksWithCRLF() {
          const auto cr_lf = QByteArrayLiteral("\x00\x11\x0d\x0a\x33");
          const auto cr_lf_fixed = QByteArrayLiteral("\x00\x11\x0a\x33");
          for (int i = 0; i < cr_lf.size(); ++i)
             QCOMPARE(getFixed(cr_lf, i), (Result{cr_lf_fixed, true}));
       }
       Q_SLOT void worksWithCRCRLF() {
          for (int i = 0; i < data.size(); ++i) QCOMPARE(getFixed(data, i).d, dataFixed);
       }
       Q_SLOT void worksWithQProcess() {
          QProcess proc;
          proc.start(QCoreApplication::applicationFilePath(), {"output"},
                     QIODevice::ReadOnly);
          proc.waitForFinished(5000);
          QCOMPARE(proc.exitCode(), 0);
          QCOMPARE(proc.exitStatus(), QProcess::NormalExit);
    
          QByteArray out = proc.readAllStandardOutput();
          QByteArray fixed;
          appendBinFix(fixed, out);
          QCOMPARE(out, data);
          QCOMPARE(fixed, dataFixed);
       }
    };
    
    int main(int argc, char *argv[]) {
       QCoreApplication app(argc, argv);
       if (app.arguments().size() > 1) return writeOutput();
       AppendTest test;
       QTEST_SET_MAIN_SOURCE_PATH
       return QTest::qExec(&test, argc, argv);
    }
    #include "main.moc"