fonZ
fonZ

Reputation: 2479

Qt 5 and QProcess redirect stdout with signal/slot readyRead

This problem is bothering me because it should work, but sadly it does not. What i try to achieve is to read the standard output of a certain process and make another process handle it i.e. print it out.

The process that produces output looks like this:

#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;
}

The process is started in another application like this:

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

My expected output would be:

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

However i get no output at all. And when i use QCoreApplication i get all the output but not through the signal/slot but directly in the console.

I dont understand because it works in another application that uses Qt 4.8.

My question is, is anyone experiencing the same problem or does anyone know how i can get the expected behaviour?

Upvotes: 5

Views: 10676

Answers (3)

Your problem in the answer you provide lies in misunderstanding how the reading works. It simply returns whatever data you've got there, whether there are line endings or not. By spawning a thread and sleeping between lines, you're effectively sending the inter-process data in line-sized chunks since the pipe is flushed when you wait long enough.

So, your answer, while working, is not really how one should do it. You need to use readLine() to chop the incoming data into lines. Below is an example with following qualities:

  1. There's just one executable :)
  2. Only Qt apis are used. This reduces the runtime memory consumption.
  3. Both processes cleanly terminate.
  4. The amount of code is as minimal as practicable.

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

Upvotes: 4

fonZ
fonZ

Reputation: 2479

Well i solved my problem.

If the process is started with startDetached() it will not receive the signals from readyRead(), readyReadStandardOutput() and readyReadStandardError().

So just starting it with start() solved the problem.

However i noticed that if i start and do the while loop and prints in main() it will read everything at once even if it ends with \n. So i started the while loop in a thread and that problem was also solved. Everything prints as expected.

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

Upvotes: -1

TheDarkKnight
TheDarkKnight

Reputation: 27611

Based on the code you've posted, you're connecting to the class slot with this: -

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

But the function in the class is declared like this: -

void SomeClass::onReadyRead();

If you're expecting onReadyRead to be called, then you should be calling it in the SLOT, rather than onReadyReadStdOutput. So change your connection to: -

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

Upvotes: 0

Related Questions