【问题标题】:Qt 5 and QProcess redirect stdout with signal/slot readyReadQt 5 和 QProcess 使用信号/插槽 readyRead 重定向标准输出
【发布时间】:2013-07-25 06:43:03
【问题描述】:

这个问题困扰着我,因为它应该可以工作,但遗憾的是它没有。 我试图实现的是读取某个进程的标准输出并让另一个进程处理它,即打印出来。

产生输出的过程如下所示:

#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 的应用程序中工作。

我的问题是,有没有人遇到同样的问题,或者有谁知道我怎样才能得到预期的行为?

【问题讨论】:

    标签: c++ c qt5


    【解决方案1】:

    您提供的答案中的问题在于误解了阅读的工作原理。它只是返回您在那里获得的任何数据,无论是否有行尾。通过生成线程并在行之间休眠,您可以有效地以行大小的块发送进程间数据,因为当您等待足够长的时间时管道会被刷新。

    因此,您在工作时的回答并不是真正应该如何去做。您需要使用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 信号,我解释了为什么它没有这样做。我每天都写套接字,所以我完全知道你可以在找到 \n 时使用 readLine() 来读取数据。我问自己的是,为什么您认为要读取的进程的数据中应该有一个 \n ?因为它完全有可能不包含\n。此外,如果您使用数据,您的数据中可能会有一个 \n,这将具有完全不同的含义。
    • 确实不需要将代码拆分为文件。不适用于 Qt,但 Qt 只是一个 C++ 框架。这意味着它是建立在 C++ 之上的。由于 C++ 是用 C 构建的,C 也不需要不同的文件。但是,我更喜欢尽可能多地划分所有内容,因为这可以提高程序的可读性,而 C++ 意味着 OO(面向对象)。这些是最佳做法,不是我个人的偏好。
    • 等一下 - 你自己说过,我引用:如果我开始并执行 while 循环并在 main() 中打印,它会立即读取所有内容,即使它以\n. 那么,你现在为什么自相矛盾?
    • 你误解了这个问题。问题是包含 \n 的垃圾数据的可执行文件没有刷新(在主线程中),即使 fflush 被明确调用。应用程序停止时刷新缓冲区。所以我的 QProcess 甚至从未收到过信号,因此它永远不会在不关闭应用程序的情况下读取输出。 ;) 将 while 循环放在线程中解决了这个问题。
    • 好吧,这段代码不使用线程就可以工作,所以我看不到问题:)
    【解决方案2】:

    我解决了我的问题。

    如果进程以 startDetached() 启动,它将不会收到来自 readyRead()readyReadStandardOutput()readyReadStandardError() 的信号>.

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

    但是我注意到,如果我开始并执行 while 循环并在 main() 中打印,它会立即读取所有内容,即使它以 \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();
    }
    

    TestP 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();
    }
    

    【讨论】:

    • 一般来说,每当我看到 QThread 在 stackoverflow 答案中使用时,有超过 50% 的机会是无缘无故地使用它,或者充其量是由于某人不熟悉 Qt 造成的。所以,是的,虽然你的答案确实“解决”了你的问题,但这根本不是一个好的答案。这里就是这种情况。
    • 好吧,Kuba,您不知道为什么使用该线程,所以您的假设是错误的。而不是说有什么问题,如果你知道更好的答案,请发布它......我正在等待它。
    • 但是您没有在问题中使用 startDetached 开始该过程。
    【解决方案3】:

    根据您发布的代码,您将使用以下代码连接到类插槽:-

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

    但是类中的函数是这样声明的:-

    void SomeClass::onReadyRead();
    

    如果您希望调用 onReadyRead,那么您应该在 SLOT 中调用它,而不是 onReadyReadStdOutput。因此,将您的连接更改为:-

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

    【讨论】:

    • 对不起,我犯了语法错误,这实际上是我正在做的。我编辑并更正了它。
    • 您是否尝试过删除第一个进程中的睡眠以及可能的 fflush 调用;有了 \n 你就不需要那个了吗?
    • 我尝试了很多东西,包括没有冲洗和睡眠。但这不重要,信号没有被触发,所以插槽不会接收任何东西。
    • 当你从调试器运行它时,你有没有抱怨输出窗口中的信号/槽连接?
    • 刚试过,但没有。我通过在调用时添加打印来检查插槽。据我所知,它从未被调用过。用我的 main 我启动 3 个进程,所有的插槽都连接到 readyRead 信号。 3 个进程的输出出现在终端中,就好像我分别启动它们一样。并且永远不会调用插槽中的打印。
    猜你喜欢
    • 2017-02-06
    • 1970-01-01
    • 1970-01-01
    • 2013-05-23
    • 2022-12-18
    相关资源
    最近更新 更多