【问题标题】:How to use QProcess write correctly?如何正确使用 QProcess 写入?
【发布时间】:2014-03-21 21:46:32
【问题描述】:

我需要一个程序来与依赖于 in-and 的子进程通信 输出。问题是我显然无法正确使用QProcess

下面的代码应该创建一个 QProcess,启动它并进入主 while 循环。在那里,它将子进程创建的所有输出打印到控制台,然后要求用户输入,然后通过write(...) 传递给子进程。

最初我在这种情况下遇到了两个问题:

  1. 父进程无法读取子进程的printf
  2. 子进程中的scanf 没有收到通过write 发送的字符串。

至于(1),我意识到这是由于子进程的stdout的缓冲引起的问题。通过fflush(stdout) 调用或对其刷新行为进行操作,可以轻松解决此问题。

第二个问题是我无法解决的问题。 write 被调用,甚至返回正确的发送字节数。但是,子进程不会继续执行,因为没有新数据写入其输出。 scanf 似乎没有收到发送的数据。程序给出的输出是:

Subprocess should have started.
124 bytes available!
Attempting to read:
Read: This is a simple demo application.
Read: It solely reads stdin and echoes its contents.
Read: Input exit to terminate.
Read: ---------
Awaiting user input: test
Written 5 bytes
No line to be read...
Awaiting user input: 

我严重卡在这里。 Google + 沉重的思考让我失败了,我想把它传递给你,作为我最后的希望灯塔。如果我只是没有看到所有树木的森林,我很抱歉。

如果需要此信息:我正在使用 Qt5 和 clang 编译器在 64 位 MacOS X 上工作。子进程代码在同一台机器上用 gcc 编译。

非常感谢您,

NR

主代码:

int main() {
    // Command to execute the subprocess
    QString program = "./demo";
    QProcess sub;
    sub.start(program, QProcess::Unbuffered | QProcess::ReadWrite);

    // Check, whether the subprocess is starting correctly.
    if (!sub.waitForStarted()) {
        std::cout << "Subprocess could not be started!" << std::endl;
        sub.close();
        return 99;
    }

    std::cout << "Subprocess should have started." << std::endl;

    // Check, if the subprocess has written its starting message to the output.
    if (!sub.waitForReadyRead()) {
        std::cout << "No data available for reading. An error must have occurred." << std::endl;
        sub.close();
        return 99;
    }

    while (1) {
        // Try to read the subprocess' output
        if (!sub.canReadLine()) {
            std::cout << "No line to be read..." << std::endl;
        } else {
            std::cout << sub.bytesAvailable() << " bytes available!" << std::endl;
            std::cout << "Attempting to read..." << std::endl;
            while (sub.canReadLine()) {
                QByteArray output = sub.readLine();
                std::cout << "Read: " << output.data();
            }
        }

        std::cout << "Awaiting user input: ";
        std::string input;
        getline(std::cin, input);

        if (input.compare("exit") == 0) break;

        qint64 a = sub.write(input.c_str());
        qint64 b = sub.write("\n");
        sub.waitForBytesWritten();
        std::cout << "Written " << a + b << " bytes" << std::endl;
    }

    std::cout << "Terminating..." << std::endl;
    sub.close();
}

子流程代码:

int main() {
    printf("This is a simple demo application.\n");
    printf("It reads stdin and echoes its contents.\n");
    printf("Input \"exit\" to terminate.\n");

    while (1) {
        char str[256];
        printf("Input: ");
        fflush(stdout);
        scanf("%s", str);

        if (strcmp(str, "exit") == 0) return 0;

        printf("> %s\n", str);
    }
}

P.s:由于这是我关于 SO 的第一个问题,请告诉我提问方式是否有问题。


解决方案

经过多次尝试和错误,我设法想出了解决问题的方法。添加对waitForReadyRead() 的调用会导致主进程等待子进程写入新输出。工作代码是:

...
sub.waitForBytesWritten();
std::cout << "Written " << a + b << " bytes" << std::endl;
// Wait for new output
sub.waitForReadyRead();
...

我仍然不知道它为什么会这样工作。我想这在某种程度上与getline() 阻塞主进程与waitForReadyRead() 阻塞有关。在我看来,getline() 似乎阻止了所有内容,包括子进程,导致由于竞争条件而永远不会处理 scanf 调用。

如果有懂的人可以放弃解释就好了。

感谢您的帮助:)

NR

【问题讨论】:

  • 您在sub.write("\n") 之后尝试过sub.waitForBytesWritten() 吗?编辑:*.com/questions/4949548/…
  • 我刚试过,结果没有改变。子进程仍然拒绝产生新的输出。
  • 想一想,这不就是目前为止的预期结果吗?您写出数据,确实将其发送到子进程,而子进程甚至可能已收到并处理它,但我希望父进程会快得多在到达if (!sub.canReadLine()) { 检查。如果是这样,那么在你的子进程有时间做出反应之前,你的父进程又回到等待你的输入,而不是子进程'。能不能多写点输入,看看到时候子流程有没有输出?
  • sub.canReadLine() 始终为假,无论写入多少输入。调用 sub.bytesAvailable() 也总是返回相同的数字。
  • bool QIODevice::canReadLine () const [virtual] 如果可以从设备读取一整行数据,则返回 true;否则返回假。请注意,无法确定可以读取什么的无缓冲设备总是返回 false。 qt-project.org/doc/qt-4.8/qiodevice.html#canReadLine 碰巧你将QProcess 声明为QProcess::Unbuffered...

标签: c++ qt scanf qprocess


【解决方案1】:

这行不通。您正在等待写入发送的字节,但不是在等待回显。相反,您正在输入等待新用户输入的 getline() 函数。请记住,这里涉及两个过程,每个过程都可以延迟到任何程度。

除此之外,您应该考虑异步构建您的 Qt 应用程序(具有事件循环),而不是尝试同步方法。这样,您的 Qt 应用程序可以并行执行操作……例如读取输入或等待来自远程进程的输入,同时仍未被阻止并能够接受用户输入。

【讨论】:

  • 我猜你的解释比我在评论中的解释要好。也许是关于比赛条件的简介。
  • 那么QProcess是和主进程同步运行的,也就是说getline()有效地阻塞了子进程?
  • 没有。在其他进程发送回显之前,您正在输入 getline()。在等待 getline() (这是一个阻塞函数调用!)时,您无法从其他进程读取任何延迟的回显。
  • 好的。因此,当子进程打印最后一次写入的回显时,主程序卡在 getline 上。因此,主进程不会立即显示回声。现在我提供更多输入,导致循环重新启动。现在不应该提供子流程的最后一次打印吗?因为这没有发生。无论我提供多少次输入或等待多长时间,都没有发生任何事情。
  • 用答案编辑了问题。看来比赛条件确实是罪魁祸首。尽管我不知道为什么,但它现在有效。所以非常感谢你们:)