Qt 5 and Qprocess Redirect Stdout with Signal/Slot Readyread

Qt 5 and QProcess redirect stdout with signal/slot readyRead

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

QProcess saving to QTextEdit

It's crashing because you're deleting the sender object while inside a slot. Instead of delete process, you should

process->deleteLater();

For logging purposes you should be using QPlainTextEdit instead of a QTextEdit. The former is faster. You're prematurely pessimizing by using the latter. Alas, even QPlainTextEdit becomes abysmally slow if you're sending about 100 lines/s (at least on Qt 4.8). If you want a really fast log view, you'll need to use QListWidget, with a caveat, or roll your own.

I have a complete example of how to send to and receive from a process in another answer.

QProcess problems, output of process

eI see one big problem.
Under windows you issue a commend pressing the Enter key. Writing

cmd.write("command");
cmd.write("\n");


just isn't enough you have to write

cmd.write("command");
cmd.write("\n\r");

Notice the trailing \r. Try this, it should work better, and by better I mean 7zip. I don't know if you'll get ipconfig to work properly.

Good luck and best regards

D

EDIT
Here is a working solution:


#include <QtCore/QCoreApplication>
#include <QtCore/QProcess>
#include <QtCore/QString>
#include <QtCore/QTextStream>

// Not clean, but fast
QProcess *g_process = NULL;

// Needed as a signal catcher
class ProcOut : public QObject
{
Q_OBJECT
public:
ProcOut (QObject *parent = NULL);
virtual ~ProcOut() {};

public slots:
void readyRead();
void finished();
};

ProcOut::ProcOut (QObject *parent /* = NULL */):
QObject(parent)
{}

void
ProcOut::readyRead()
{
if (!g_process)
return;

QTextStream out(stdout);
out << g_process->readAllStandardOutput() << endl;
}

void
ProcOut::finished()
{
QCoreApplication::exit (0);
}

int main (int argc, char **argv)
{
QCoreApplication *app = new QCoreApplication (argc, argv);

ProcOut *procOut = new ProcOut();
g_process = new QProcess();

QObject::connect (g_process, SIGNAL(readyReadStandardOutput()),
procOut, SLOT(readyRead()));
QObject::connect (g_process, SIGNAL(finished (int, QProcess::ExitStatus)),
procOut, SLOT(finished()));

g_process->start (QLatin1String ("cmd"));
g_process->waitForStarted();

g_process->write ("ipconfig\n\r");

// Or cmd won't quit
g_process->write ("exit\n\r");

int result = app->exec();

// Allright, process finished.
delete procOut;
procOut = NULL;

delete g_process;
g_process = NULL;

delete app;
app = NULL;

// Lets us see the results
system ("pause");

return result;
}

#include "main.moc"

Hope that helps. It worked everytime on my machine.

Start QProcess from within QDialog that is used as a progress monitor

Here is an example how you can do it (I use QWidget but you can also use QDialog or whatever). I don't use a separate thread because the UI doesn't need to be interactive. If you want to add buttons etc. then you should consider going for the good old QThread running a QObject model provided by Qt.

#!/usr/bin/python
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import sys

class MyQProcess(QWidget):
def __init__(self):
super(QWidget, self).__init__()

# Add the UI components (here we use a QTextEdit to display the stdout from the process)
layout = QVBoxLayout()
self.edit = QTextEdit()
self.edit.setWindowTitle("QTextEdit Standard Output Redirection")
layout.addWidget(self.edit)
self.setLayout(layout)

# Add the process and start it
self.process = QProcess()
self.setupProcess()

# Show the widget
self.show()

def setupProcess(self):
# Set the channels
self.process.setProcessChannelMode(QProcess.MergedChannels)
# Connect the signal readyReadStandardOutput to the slot of the widget
self.process.readyReadStandardOutput.connect(self.readStdOutput)
# Run the process with a given command
self.process.start("df -h")

def __del__(self):
# If QApplication is closed attempt to kill the process
self.process.terminate()
# Wait for Xms and then elevate the situation to terminate
if not self.process.waitForFinished(10000):
self.process.kill()

@pyqtSlot()
def readStdOutput(self):
# Every time the process has something to output we attach it to the QTextEdit
self.edit.append(QString(self.process.readAllStandardOutput()))

def main():
app = QApplication(sys.argv)
w = MyQProcess()

return app.exec_()

if __name__ == '__main__':
main()

Notice that the command I'm using (df -h) runs once (it's a Linux command which displays the disk usage on your hard drives) and then it's over. You can replace it also with your Execute.exe which can run indefinitely. I have tested it with htop (a terminal-based advanced task manager), which once started doesn't stop unless the user wants it to or the system stops (crash, shutdown etc.).

Note that you have to ensure that the external process is stopped in a clean manner. This can be done inside __del__ (destructor) or another function invoked at the end of the life of a given widget. What I've done is basically send a SIGTERM (terminate) to the external process and once a given amount of time has passed but the process is still running I elevate the situation to SIGKILL (kill).

The code needs more work obviously but it should be enough to give you an idea how things work.

Here is the same version of the code above but with an extra thread. Note that I am redirecting the output from the external process to a slot in my worker. You don't have to do that unless you want to maybe work on that output. So you can skip this and connect your process signal to the slot in your widget that receives it and outputs its content. The processing of the output will be done again inside the separate thread so you can go the distance instead of freezing your UI (which will happen if you follow the

from PyQt4.QtGui import * 
from PyQt4.QtCore import *
import sys

class Worker(QObject):
sendOutput = pyqtSignal(QString)

def __init__(self):
super(Worker, self).__init__()
self.process = QProcess()
self.setupProcess()

def __del__(self):
self.process.terminate()
if not self.process.waitForFinished(10000):
self.process.kill()

def setupProcess(self):
self.process.setProcessChannelMode(QProcess.MergedChannels)
self.process.readyReadStandardOutput.connect(self.readStdOutput)
self.process.start("htop")

@pyqtSlot()
def readStdOutput(self):
output = QString(self.process.readAllStandardOutput())
# Do some extra processing of the output here if required
# ...
self.sendOutput.emit(output)

class MyQProcess(QWidget):
def __init__(self):
super(QWidget, self).__init__()
layout = QVBoxLayout()
self.edit = QTextEdit()
self.thread = QThread()

self.setupConnections()

self.edit.setWindowTitle("QTextEdit Standard Output Redirection")
layout.addWidget(self.edit)
self.setLayout(layout)
self.show()

def setupConnections(self):
self.worker = Worker()
self.thread.finished.connect(self.worker.deleteLater)
self.worker.sendOutput.connect(self.showOutput)

self.worker.moveToThread(self.thread)
self.thread.start()

def __del__(self):
if self.thread.isRunning():
self.thread.quit()
# Do some extra checking if thread has finished or not here if you want to

#Define Slot Here
@pyqtSlot(QString)
def showOutput(self, output):
#self.edit.clear()
self.edit.append(output)

def main():
app = QApplication(sys.argv)
w = MyQProcess()

return app.exec_()

if __name__ == '__main__':
main()

Further clarification:
As I've told @BrendanAbel in the comment section of his answer the issue with using slots with QThread is that the slots have the same thread affinity (=the thread they belong to) as the QThread instance itself, which is the same thread where the QThread was created from. The only - I repeat the only - thing that runs in a separate thread when it comes to a QThread is its event loop represented by QThread.run(). If you look on the Internet you will find out that this way of doing things is discouraged (unless you really, really know that you have to subclass QThread) because since the early versions of Qt 4 run() was abstract and you had to subclass QThread in order to use a QThread. Later the abstract run() got a concrete implementation hence the need of subclassing QThread was removed. About thread-safety and signals what @BrendanAbel wrote is only partially true. It comes down to the connection type (default is AutoConnection). If you manually specify the connection type you may actually render the signals thread-unsafe. Read more about this in the Qt documentation.

Qt Logging tool multithreading, calling signal and slot with variable number of arguments form another thread, mixing C and C++

Here are my nitpicks, arranged in the order of how I thought about it.

  1. The declaration and definition of r_printf differ. Both must be EXPORT_C.

  2. EXPORT_C is likely to clash with some errant library code. Prefer a more unique name, like LOG_H_EXPORT_C.

  3. A signal can have zero or more recipients. The message allocated in r_printf can in principle leak or be multiply deleted. There's no reason to do manual memory management here, use a shared pointer or an implicitly shared data structure like QByteArray instead.

  4. For portability, you could use qvsnprintf.

  5. You're allocating a huge buffer. This is a tradeoff between allocation size and performance. You have an option of calling qvsnprintf with zero size to get the needed buffer size, then allocate a correctly-sized buffer, and call qvsnprintf again. You'd need to profile this to make an informed choice. At the very least, don't allocate a page-sized buffer since on some platforms this pessimizes and allocates more than a page, at a 100% overhead. A 0xFE0 size would be a safer bet.

  6. Prefer QString::asprintf, and simply pass a QString through the slots. This guarantees that the string will be converted from 8-bit to UTF-16 encoding only once.

  7. Since you're now emitting a container like QString or QByteArray, you could factor out the log message source into a separate object. It could be connected to zero or more views, then.

  8. Do not reset the log text. Instead, use QPlainText::appendPlainText. This will avoid the need to re-parse the entire log every time you add to it.

  9. The QPlainTextEdit is abysmally slow and unsuitable for logging. You should use a QListView or a custom widget instead.

  10. You may wish to keep the log scrolled to the bottom if it already is so. See this question for details.

Here's an example:

screenshot of the example

Log.h

#ifndef LOG_H
#define LOG_H

#ifdef __cplusplus
#include <QObject>
class Log : public QObject {
Q_OBJECT
public:
/// Indicates that a new message is available to be logged.
Q_SIGNAL void newMessage(const QString &);
/// Sends a new message signal from the global singleton. This method is thread-safe.
static void sendMessage(const QString &);
/// Returns a global singleton. This method is thread-safe.
static Log * instance();
};
#define LOG_H_EXPORT_C extern "C"
#else
#define LOG_H_EXPORT_C
#endif

LOG_H_EXPORT_C void r_printf(const char * format, ...);

#endif // LOG_H

Log.cpp

#include "Log.h"
#include <cstdarg>

Q_GLOBAL_STATIC(Log, log)

Log * Log::instance() { return log; }

void Log::sendMessage(const QString & msg) {
emit log->newMessage(msg);
}

LOG_H_EXPORT_C void r_printf(const char * format, ...) {
va_list argList;
va_start(argList, format);
auto msg = QString::vasprintf(format, argList);
va_end(argList);
Log::sendMessage(msg);
}

main.cpp

// https://github.com/KubaO/stackoverflown/tree/master/questions/simplelog-38793887
#include <QtWidgets>
#include <QtConcurrent>
#include "Log.h"

int main(int argc, char ** argv) {
using Q = QObject;
QApplication app{argc, argv};
QStringListModel model;
Q::connect(Log::instance(), &Log::newMessage, &model, [&](const QString & msg) {
auto row = model.rowCount();
model.insertRow(row);
model.setData(model.index(row), msg);
});
QWidget w;
QVBoxLayout layout{&w};
QListView view;
bool viewAtBottom = false;
QPushButton clear{"Clear"};
layout.addWidget(&view);
layout.addWidget(&clear);
Q::connect(&clear, &QPushButton::clicked,
&model, [&]{ model.setStringList(QStringList{}); });
view.setModel(&model);
view.setUniformItemSizes(true);
Q::connect(view.model(), &QAbstractItemModel::rowsAboutToBeInserted, &view, [&] {
auto bar = view.verticalScrollBar();
viewAtBottom = bar ? (bar->value() == bar->maximum()) : false;
});
Q::connect(view.model(), &QAbstractItemModel::rowsInserted,
&view, [&]{ if (viewAtBottom) view.scrollToBottom(); });

QtConcurrent::run([]{
auto delay = 10;
for (int ms = 0; ms <= 500; ms += delay) {
r_printf("%d ms", ms);
QThread::msleep(ms);
}
});
w.show();
return app.exec();
}

launching a program inside another program

  1. In a C/C++ string literal, you must escape all backward slashes.

  2. It's really bad to use the waitForX() functions in Qt. They block your GUI and make your application unresponsive. From a user experience point of view, it truly sucks. Don't do it.

You should code in asynchronous style, with signals and slots.

My other answer provides a rather complete example how asynchronous process communications might work. It uses QProcess to launch itself.

Your original code could be modified as follows:

class Window : ... {
Q_OBJECT
Q_SLOT void launch() {
const QString program = "C:\\A2Q1-build-desktop\\debug\\A2Q1.exe";
QProcess *process = new QProcess(this);
connect(process, SIGNAL(finished(int)), SLOT(finished()));
connect(process, SIGNAL(error(QProcess::ProcessError)), SLOT(finished()));
process->start(program);
}
Q_SLOT void finished() {
QScopedPointer<Process> process = qobject_cast<QProcess*>(sender());
QString out = process->readAllStandardOutput();
// The string will be empty if the process failed to start
... /* process the process's output here */
// The scoped pointer will delete the process at the end
// of the current scope - right here.
}
...
}

How to communicate Qt applications two-way

It's very hard to explain in one answer all mistakes, so just look at code and ask if you still got problems.
Here is example of using QProcess as IPC.

This is your main process, that creates additional process and connects to its signals

MyApplicaiton.h

#ifndef MYAPPLICATION_H
#define MYAPPLICATION_H
#include <QApplication>

class InterProcess;
class MyApplication : public QApplication {
Q_OBJECT
public:
MyApplication(int &argc, char **argv);
signals:
void mainApplicationSignal();
private slots:
void onInterProcessSignal();
private:
InterProcess *mProcess;
};
#endif // MYAPPLICATION_H

MyApplicaiton.cpp

#include "MyApplication.h"
#include "InterProcess.h"

MyApplication::MyApplication(int &argc, char **argv) : QApplication(argc, argv) {
mProcess = new InterProcess(this);
connect(mProcess, SIGNAL(interProcessSignal()),
this, SLOT(onInterProcessSignal()));
mProcess->start();
}

void MyApplication::onInterProcessSignal() {}

This is example implementation of your interProcess class:

InterProcess.h

class InterProcess : public QProcess {
Q_OBJECT
public:
explicit InterProcess(QObject *parent = nullptr);
signals:
void interProcessSignal();
private slots:
void onMainApplicationSignal();
};

InterProcess.cpp

#include "InterProcess.h"
#include "MyApplication.h"

InterProcess::InterProcess(QObject *parent) : QProcess(parent) {
if(parent) {
auto myApp = qobject_cast<MyApplication *>(parent);
if(myApp) {
connect(myApp, SIGNAL(mainApplicationSignal()),
this, SLOT(onMainApplicationSignal()));
}
}
}

void InterProcess::onMainApplicationSignal() {}

How to redirect qDebug, qWarning, qCritical etc output?

You've to install a message handler using qInstallMessageHandler function, and then, you can use QTextStream to write the debug message to a file. Here is a sample example:

#include <QtGlobal>
#include <stdio.h>
#include <stdlib.h>

void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
QByteArray localMsg = msg.toLocal8Bit();
switch (type) {
case QtDebugMsg:
fprintf(stderr, "Debug: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
break;
case QtInfoMsg:
fprintf(stderr, "Info: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
break;
case QtWarningMsg:
fprintf(stderr, "Warning: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
break;
case QtCriticalMsg:
fprintf(stderr, "Critical: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
break;
case QtFatalMsg:
fprintf(stderr, "Fatal: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
abort();
}
}

int main(int argc, char **argv)
{
qInstallMessageHandler(myMessageOutput); // Install the handler
QApplication app(argc, argv);
...
return app.exec();
}

Taken from the doc of qInstallMessageHandler (I only added the comments):

  • QtMsgHandler qInstallMessageHandler ( QtMsgHandler handler )

In the above example, the function myMessageOutput uses stderr which you might want to replace with some other file stream, or completely re-write the function!

Once you write and install this function, all your qDebug (as well as qWarning, qCritical etc) messages would be redirected to the file you're writing to in the handler.



Related Topics



Leave a reply



Submit