4

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?

fonZ
  • 2,428
  • 4
  • 21
  • 40

3 Answers3

4

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"
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • I think you missed the problem, the problem was that it was not emitting the readyRead signal and i explained why it didnt do that. I write sockets every day, so i perfectly know that you can use readLine() to read data when a \n is found. What i ask myself is why you assume there should be a \n in the data from the processes you want to read? Because it's perfectly possible that it does not contain \n. Furthermore if you work with data you could have a \n in your data which would have a total different meaning. – fonZ Sep 04 '13 at 11:53
  • Splitting code into files is indeed not needed. Not for Qt, but Qt is just a C++ framework. Which means it was built on top of C++. And because of the fact that C++ was built with C, C doesnt require different files either. However i prefer to divide everything as much as i can as this improves the readability of a program and C++ is meant to be OO (Object Oriented). These are best practices, not my personal preferences. – fonZ Sep 04 '13 at 12:01
  • Wait a minute - you've said yourself, and I cite: *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, why are you now contradicting yourself? – Kuba hasn't forgotten Monica Sep 04 '13 at 17:45
  • You misunderstand the problem. The problem was that the executable that was spamming the data, which contained a \n, did not flush (in the main thread), even when fflush was explicitely called. The buffers were flushed when the application stopped. So my QProcess never even received a signal so it could never have read the output without closing the application. ;) Putting the while loop in a thread solves that problem. – fonZ Sep 06 '13 at 17:52
  • Well, this code works without using threads, so I fail to see the problem :) – Kuba hasn't forgotten Monica Sep 12 '13 at 17:38
0

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()));
TheDarkKnight
  • 27,181
  • 6
  • 55
  • 85
  • Sorry i made a syntax error, this is actually what i am doing. I edited and corrected it. – fonZ Jul 25 '13 at 12:15
  • Have you tried removing the sleep in the 1st process and possibly the fflush call; with \n you shouldn't need that anyway? – TheDarkKnight Jul 25 '13 at 12:26
  • I tried alot of things, including without flush and sleep. But that should not matter, the signal is not fired so the slot doesnt pick anything up. – fonZ Jul 25 '13 at 12:45
  • When you run it from the debugger, do you get any complaint about the signal / slot connection in the output window? – TheDarkKnight Jul 25 '13 at 12:51
  • Just tried but nothing. I check the slot by adding a print when it is called. As far as i can see it is never called. With my main i start 3 processes, all with the slots connected to the readyRead signal. The output of the 3 processes appear in the terminal as if i started them separately. And the print in the slot is never called. – fonZ Jul 25 '13 at 13:51
  • by complaint i assume you refer to "the slot could not be connected to the signal" error message. I dont get that warning, triple checked the signal and slots are correct. – fonZ Jul 25 '13 at 13:55
  • Yes, that was what I meant. Have you tried putting a break point in the slot and seeing if it gets triggered? You could also try calling waitForFinished and check to see if there's an error in the QProcess. – TheDarkKnight Jul 25 '13 at 14:06
  • The signal is never called, that is the problem. Consequently the slot is never called. The processes dont have errors, i test them separately and everything works fine. I use http://harmattan-dev.nokia.com/docs/library/html/qt4/unix-signals.html to handle unix signals but i doubt that is the problem. And i use http://stackoverflow.com/questions/4954140/how-to-redirect-qdebug-qwarning-qcritical-etc-output to handle output with std::cout. – fonZ Jul 25 '13 at 14:24
  • Have you tried connecting to the QProcess started and error signals and do they emit correctly? – TheDarkKnight Jul 25 '13 at 14:32
  • Just did that. the processes start correctly and no errors appear. – fonZ Jul 25 '13 at 14:50
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/34167/discussion-between-merlin069-and-fonz) – TheDarkKnight Jul 25 '13 at 15:01
-1

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();
}
fonZ
  • 2,428
  • 4
  • 21
  • 40
  • 1
    In general, whenever I see `QThread` used in a stackoverflow answer, there's a better than 50% chance that it's used for either no reason, or at best a reason stemming from someone's unfamiliarity with Qt. So yeah, while your answer does "fix" your problem, it's not a good answer at all. This is the case here. – Kuba hasn't forgotten Monica Sep 04 '13 at 18:15
  • Well Kuba, you dont know why the thread has been used so your assumption is just wrong. And instead of saying something is wrong, then please IF you know a better answer post it... im waiting for it. – fonZ Dec 13 '13 at 16:46
  • But you are not starting the process with startDetached in the question. – darklon Jan 10 '15 at 12:57