Qt 5和QProcess如何使用信号/槽机制重定向标准输出流readyRead?

4
这个问题困扰着我,因为它应该可以工作,但是可悲的是它没有。我尝试实现的目标是读取特定进程的标准输出,并使另一个进程处理它,即将其打印出来。
产生输出的进程如下所示:
#include <stdio.h>
#include <stdlib.h>
#include <iostream>

int main() {
    for (int i = 0; i < 100; i++) {
        printf("yes %d\n",i);
        fflush(stdout);
        sleep(1);
    }
    return 0;
}

该过程可以在另一个应用程序中以如下方式启动:
#include <QProcess>
...
QProcess * process = new QProcess;
SomeClass * someClass = new SomeClass(process);
connect(process,SIGNAL(readyRead()),someClass,SLOT(onReadyRead()));

process->start("../Test/Test",QStringList());
if (!process->waitForStarted(4000)) {
    qDebug() << "Process did not start.";
}
...
void SomeClass::onReadyRead() {
    qDebug() << "Reading:" << process->readAllStdOutput();
}

我期望的输出结果是:

Reading: yes 0
Reading: yes 1
...
Reading: yes 99

然而,我根本没有任何输出。当我使用QCoreApplication时,我可以获得所有输出,但不是通过信号/槽,而是直接在控制台中。

我不明白,因为它在另一个使用Qt 4.8的应用程序中运行。

我的问题是,是否有人遇到了同样的问题,或者是否有人知道如何获得期望的行为?

3个回答

4
您提供的答案存在一个误解,即对读取工作原理的误解。它只是返回您拥有的任何数据,无论是否有行结束符。通过生成线程并在行之间休眠,您实际上是以行大小的块发送进程间数据,因为管道在等待足够长时间后会被刷新。
因此,您的答案虽然可行,但不是应该采用的方法。您需要使用readLine()将传入的数据分割成行。下面是一个具有以下特点的示例:
  1. 只有一个可执行文件:)
  2. 仅使用Qt api。这降低了运行时内存消耗。
  3. 两个进程都干净地终止。
  4. 代码量尽可能小。
// https://github.com/KubaO/stackoverflown/tree/master/questions/process-17856897
#include <QtCore>

QTextStream out{stdout};

class Slave : public QObject {
    QBasicTimer m_timer;
    int m_iter = 0;
    void timerEvent(QTimerEvent * ev) override {
        if (ev->timerId() == m_timer.timerId()) {
            out << "iteration " << m_iter++ << endl;
            if (m_iter > 35) qApp->quit();
        }
    }
public:
    Slave(QObject *parent = nullptr) : QObject(parent) {
        m_timer.start(100, this);
    }
};

class Master : public QObject {
    Q_OBJECT
    QProcess m_proc{this};
    Q_SLOT void read() {
        while (m_proc.canReadLine()) {
            out << "read: " << m_proc.readLine();
            out.flush(); // endl implicitly flushes, so we must do the same
        }
    }
    Q_SLOT void started() {
        out << "started" << endl;
    }
    Q_SLOT void finished() {
        out << "finished" << endl;
        qApp->quit();
    }
public:
    Master(QObject *parent = nullptr) : QObject(parent) {
        connect(&m_proc, SIGNAL(readyRead()), SLOT(read()));
        connect(&m_proc, SIGNAL(started()), SLOT(started()));
        connect(&m_proc, SIGNAL(finished(int)), SLOT(finished()));
        m_proc.start(qApp->applicationFilePath(), {"dummy"});
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication app{argc, argv};
    if (app.arguments().length() > 1)
        new Slave{&app}; // called with an argument, this is the slave process
    else
        new Master{&app}; // no arguments, this is the master
    return app.exec();
}

#include "main.moc"

我认为你错过了问题,问题在于它没有发出readyRead信号,而我解释了为什么它没有这样做。我每天都写套接字,所以我非常清楚你可以使用readLine()在找到\n时读取数据。我想知道的是,为什么你认为要从你要读取的进程中的数据中有一个\n?因为这是完全可能不包含\n的。此外,如果你处理数据,你的数据中可能会有一个\n,这将具有完全不同的含义。 - fonZ
把代码分成多个文件其实并不是必需的。虽然Qt本身不需要,但Qt只是一个C++框架。这意味着它是在C++之上构建的。而由于C++是基于C语言构建的,所以C语言也不需要使用不同的文件。然而,我更喜欢尽可能将一切分开,因为这可以提高程序的可读性,而且C++本来就应该是面向对象的。这些都是最佳实践,而不是我的个人偏好。 - fonZ
等一下 - 你自己说过,我引用一下:如果我在主函数中开始并执行 while 循环并打印输出,它将一次性读取所有内容,即使以 \n 结尾。 那么,为什么你现在又自相矛盾了呢? - Kuba hasn't forgotten Monica
你误解了问题。问题在于那个正在发送数据的可执行文件包含一个 \n,即使明确调用 fflush,在主线程中也没有刷新缓冲区。只有当应用程序停止时,缓冲区才会被刷新。因此,我的 QProcess 甚至没有接收到信号,所以它永远无法在不关闭应用程序的情况下读取输出。;) 将 while 循环放在一个线程中可以解决这个问题。 - fonZ
这段代码在没有使用线程的情况下就能正常工作,所以我看不出有什么问题 :) - Kuba hasn't forgotten Monica

0

根据您发布的代码,您正在使用以下内容连接到类插槽:

connect(process,SIGNAL(readyRead()),someClass,SLOT(onReadyReadStdOutput()));

但是类中的函数被声明为这样:-
void SomeClass::onReadyRead();

如果你期望 onReadyRead 被调用,那么你应该在 SLOT 中调用它,而不是 onReadyReadStdOutput。因此将你的连接更改为: -
 connect(process,SIGNAL(readyRead()),someClass,SLOT(onReadyRead()));

你尝试过在第一个进程中去掉sleep和可能的fflush调用吗?因为有了\n,你不需要它们。 - TheDarkKnight
我尝试了很多方法,包括不使用flush和sleep。但这并不重要,因为信号没有被触发,所以槽函数没有接收到任何东西。 - fonZ
当你在调试器中运行它时,输出窗口是否会有任何关于信号/槽连接的投诉? - TheDarkKnight
刚尝试了一下,但没反应。我通过添加一个打印语句检查它被调用的情况。据我所见,它从未被调用过。在我的主程序中,我启动了三个进程,所有进程的插槽都连接到readyRead信号。这三个进程的输出会像是我分别启动了它们一样出现在终端上。而插槽中的打印语句从未被调用过。 - fonZ
通过投诉,我认为您指的是“插槽无法连接到信号”的错误消息。我没有收到那个警告,三重检查了信号和插槽是否正确。 - fonZ
显示剩余6条评论

-1

好的,我解决了我的问题。

如果使用startDetached()启动进程,则不会接收到readyRead()readyReadStandardOutput()readyReadStandardError()信号。

所以只需使用start()启动它即可解决问题。

然而,我注意到如果我在main()中启动并执行while循环和打印操作,即使以\n结尾,它也会一次性读取所有内容。因此,我在一个线程中启动了while循环,这个问题也得到了解决。所有内容都按预期打印出来。

#include <QThread>

class Thread : public QThread 
{
    Q_OBJECT

public:
    explicit Thread(QObject *parent = 0) : QThread(parent) {}

protected:
    void run() {
        for (int i = 0; i < 100; i++) {
            std::cout << "yes" << i << std::endl;
            msleep(200);
        }
        exit(0);
    }
};

int main(int argc, char ** argv) {
    QCoreApplication app(argc,argv);
    Thread * t = new Thread();
    t->start();
    return app.exec();
}

测试P main.cpp

#include <QProcess>
#include <iostream>

class Controller : public QObject 
{
    Q_OBJECT
private:
    QProcess * process;

public:
    Controller(QObject *parent = 0) : 
        QObject(parent), process(new QProcess) {}

    void init(const QString &program) {
        connect(process,SIGNAL(readyRead()),this,SLOT(readStdOut()));
        connect(process,SIGNAL(started()),this,SLOT(onStarted()));
        connect(process,SIGNAL(finished(int)),this,SLOT(onFinished(int)));
        process->start(program);
    }

private slots:
    void readStdOut() {
        std::cout << "YES " << QString(process->readAllStandardOutput()).toUtf8().constData() << std::endl;
    }
    void onStarted(){
        std::cout << "Process started" << std::endl;
    }
    void onFinished(int) {
        std::cout << "Process finished: " << signal << std::endl;
    }
};

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);
    Controller c;
    c.init("../Test/Test");
    return a.exec();
}

1
通常情况下,每当我在stackoverflow上看到QThread的使用时,有超过50%的可能性是没有必要的,或者最多只是因为某人对Qt不熟悉而使用。所以,虽然您的答案确实“解决”了问题,但它并不是一个好的答案。这就是这种情况。 - Kuba hasn't forgotten Monica
嗯,Kuba,你不知道为什么要使用线程,所以你的假设是错误的。而且,如果你知道更好的答案,请发表出来,而不是说某些东西是错的...我在等待着。 - fonZ
但是你在问题中没有使用startDetached来启动进程。 - darklon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接