在 Windows 中从 QProcess 读取二进制数据

Read binary data from QProcess in Windows

我有一些 .exe 文件(比如 some.exe)写入标准输出 binary 数据。我没有这个程序的来源。我需要从我的 C++/Qt 应用程序 运行 some.exe 并读取我创建的进程的标准输出。当我尝试使用 QProcess::readAll 执行此操作时,有人将字节 \n (0x0d) 替换为 \r\n (0x0a 0x0d).

这是一个代码:

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

我在 cmd.exe 中尝试将输出重定向到文件,如下所示:

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

并且用hexedit查看了out.bin,在应该0d的地方出现了0a 0d

编辑:
这是一个模拟 some.exe 行为的简单程序:

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

运行:

a.exe > out.bin

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

请注意,我无法修改 some.exe,这就是为什么我不应该像 _setmode( _fileno( stdout, BINARY ) )

那样修改我的示例

问题是:我怎么能对 QProcessWindows 或安慰 不要 CR 更改为 LF CR?

OS: Windows 7
Qt: 5.6.2

不幸的是,它与 QProcessWindows 或控制台无关。这都是关于 CRTprintffwrite 等函数正在考虑 _O_TEXT 标志以添加额外的 0x0D(仅适用于 Windows)。所以唯一的解决办法是用WriteProcessMemory修改some.exestdout字段,或者在[=19=的地址space中调用_setmode ] 使用 DLL 注入技术或修补 lib。但这是一项棘手的工作。

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

他们没有改变任何东西。 some.exe 坏了。就这样。它输出错误的东西。以文本模式输出二进制数据的人把事情搞砸了。

不过,有办法恢复。您必须实现一个解码器来修复 some.exe 的损坏输出。您知道每个 0a 前面都必须有 0d。所以你必须解析输出,如果你找到一个 0a,并且它前面有 0d,删除 0d,然后继续。或者,如果 0a 前面没有 0d,您可以中止 - some.exe 不应产生此类输出,因为它已损坏。

appendBinFix 函数获取损坏的数据并将修复后的版本附加到缓冲区。

// https://github.com/KubaO/Whosebugn/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());
}

以下测试工具确保它做正确的事情,包括模拟 some.exe(本身)的输出:

#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"