i'm trying to set up a GUI with Qt for an existing application which is meant to be run in the windows commandline. It's not just running the app with the
system()
command, but i need to interact with the existing application via command line.
The system()
command blocks the GUI when i start the existing executable. How can i run this executable in the background and trigger some inputs via my own GUI-Elements such as a button?
I want to simplify the usage of this command line tool for some of my colleagues.
It would be mainly used on windows.
Thanks for your help.
I found a solution for my needs and can do what i want to do.. Actually i'm a bit disappointed. I thought it would be something more complex.
First i have to say it's an QtQuick Application .. Maybe i should have said that earlier.
I simply outsourced the process functions to another class. This class is passed to QML via the qmlRegisterType<>()
function. I connected some signals from the process ( QProcess
) to slots in my own class and wrote my own functions to handle reading/writing data from and to the console application. With the QML-onClicked
events i can pass my parameters and strings to the console app. And with some application logic i can handle the in/out requests and timings.
WrapperClass.h
class WrapperClass: public QObject
{
Q_OBJECT
public:
explicit WrapperClass(QObject *parent = nullptr);
QProcess *process;
QString str_proc_output;
Q_INVOKABLE void startProcess();
Q_INVOKABLE void stopProcess();
Q_INVOKABLE QString getOutput();
Q_INVOKABLE void writeByte(QString str);
Q_INVOKABLE QString getAllOutput();
private:
signals:
public slots:
void mReadyRead();
void mReadyReadStandardOutput();
void mFinished(int code);
void mBytesWritten(qint64 written);
};
WrapperClass.cpp
WrapperClass::WrapperClass(QObject *parent) : QObject(parent)
{
process = new QProcess();
process->setProgram("untitled.exe");
process->setProcessChannelMode(QProcess::MergedChannels);
str_proc_output = "";
connect(process, SIGNAL(readyRead()), this, SLOT(mReadyRead()));
connect(process, SIGNAL(readyReadStandardOutput()), this, SLOT(mReadyReadStandardOutput()));
connect(process, SIGNAL(finished(int)), this, SLOT(mFinished(int)));
connect(process, SIGNAL(bytesWritten(qint64)), this, SLOT(mBytesWritten(qint64)));
}
void WrapperClass::startProcess() {
if(process->state() == QProcess::Running) {
stopProcess();
} else {
process->open(QProcess::ReadWrite);
}
}
void WrapperClass::stopProcess() {
process->close();
}
QString WrapperClass::getOutput() {
return str_proc_output;
}
QString WrapperClass::getAllOutput() {
QString str = process->readAll();
std::cout << str.toStdString() << std::endl;
return str;
}
void WrapperClass::writeByte(QString str) {
char cArr[str.length()] = {};
memcpy(cArr, str.toStdString().c_str(), str.length());
QByteArray arr = QByteArray(cArr, -1);
process->write(arr);
}
void WrapperClass::mReadyRead() {
QString s = QString(process->readAll());
std::cout << "ReadyRead: " << s.toStdString() << std::endl;
str_proc_output = s;
}
void WrapperClass::mReadyReadStandardOutput() {
QString s = QString(process->readAllStandardOutput());
std::cout << "ReadyReadStandardOutput: " << s.toStdString() << std::endl;
}
void WrapperClass::mFinished(int code) {
std::cout << "Process finished! (" << code << ')' << std::endl;
}
void WrapperClass::mBytesWritten(qint64 written) {
std::cout << "Bytes written: " << written << std::endl;
}
Main.cpp
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<WrapperClass>("com.example.WrapperClass", 0, 1, "WrapperClass");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
With registering the Cpp-Class to QML i'm able to trigger the read/write functions via Click-Events from QML-MouseArea
or Button
.
Out of curiosity, I played around with QProcess
.
I'm really impressed how easy and straight forward everything works (remembering with horror how difficult it was when we did it in the past without Qt).
Thus, I can provide my little rather MCVE for demonstration.
First I made a simple console application testQProcessIOChild.cc
:
#include <chrono>
#include <iostream>
#include <sstream>
#include <string>
#include <thread>
using namespace std;
// inspired by:
// https://try.kotlinlang.org/#/Examples/Longer%20examples/99%20Bottles%20of%20Beer/99%20Bottles%20of%20Beer.kt
string bottlesOfBeer(int n)
{
switch (n) {
case 0: return "no more bottles of beer";
case 1: return "1 bottle of beer";
default: {
ostringstream outFmt;
outFmt << n << " bottles of beer";
return outFmt.str();
}
}
}
int main()
{
enum { delay = 1000 };
for (int n;;) {
cout << "Initial number of bottles (-1 ... finish): " << flush;
if (!(cin >> n)) {
cerr << "Input error!" << endl;
continue;
}
if (n < -1) {
cerr << "Illegal input!" << endl;
continue;
}
if (n < 0) break;
if (n > 100) {
cerr << "The ministry of health warns:" << endl
<< " Abuse of alcoholics may damage your health." << endl;
n = 99;
}
cout << "Go to the store and buy some more, "
<< bottlesOfBeer(n) << " on the wall." << endl;
while (n) {
this_thread::sleep_for(chrono::milliseconds(delay));
cout << bottlesOfBeer(n) << " on the wall, "
<< bottlesOfBeer(n) << '.' << endl
<< "Take one down, pass it around, ";
--n;
cout << bottlesOfBeer(n) << " on the wall." << endl;
}
this_thread::sleep_for(chrono::milliseconds(delay));
cout << "No more bottles of beer on the wall, no more bottles of beer."
<< endl;
}
return 0;
}
I did this to have something which provides:
- usage of standard input
- usage of standard output
- usage of standard error
- certain specific time consuming behavior (to see whether and when the parent process reacts).
Second I made the Qt GUI application testQProcessIO.cc
as wrapper:
// Qt header:
#include <QtWidgets>
const char *childProgram = "./testQProcessIOChild";
int main(int argc, char **argv)
{
qDebug() << QT_VERSION_STR;
// main application
QApplication app(argc, argv);
QProcess qProcessChild;
// GUI setup
QWidget qWin;
QGridLayout qGrid;
QPushButton qBtnStart(QString::fromUtf8("Start"));
qGrid.addWidget(&qBtnStart, 0, 0);
QPushButton qBtnStop(QString::fromUtf8("Stop"));
qBtnStop.setEnabled(false);
qGrid.addWidget(&qBtnStop, 0, 1);
QLabel qLblInput(QString::fromUtf8("Input: "));
qLblInput.setEnabled(false);
qGrid.addWidget(&qLblInput, 0, 2);
QLineEdit qInput;
qInput.setEnabled(false);
qGrid.addWidget(&qInput, 0, 3);
QTextEdit qTxtLog;
qTxtLog.setReadOnly(true);
qGrid.addWidget(&qTxtLog, 1, 0, 1, 4);
qGrid.setRowStretch(1, 1);
qGrid.setColumnStretch(3, 1);
qWin.setLayout(&qGrid);
qWin.show();
// install signal handlers
QObject::connect(&qBtnStart, &QPushButton::clicked,
[&](bool) {
qProcessChild.start(QString::fromLatin1(childProgram));
});
QObject::connect(&qBtnStop, &QPushButton::clicked,
[&](bool) {
qProcessChild.kill();
});
QObject::connect(&qInput, &QLineEdit::returnPressed,
[&](){
QString text = qInput.text() + '\n';
qProcessChild.write(text.toLatin1());
});
QObject::connect(&qProcessChild, &QProcess::started,
[&]() {
qBtnStart.setEnabled(false);
qBtnStop.setEnabled(true);
qLblInput.setEnabled(true);
qInput.setEnabled(true);
});
QObject::connect(&qProcessChild,
// cast needed because QProcess::finished() is polymorph
(void(QProcess::*)(int))&QProcess::finished,
[&](int) {
qBtnStart.setEnabled(true);
qBtnStop.setEnabled(false);
qLblInput.setEnabled(false);
qInput.setEnabled(false);
qTxtLog.clear();
});
QObject::connect(&qProcessChild, &QProcess::readyReadStandardOutput,
[&]() {
qTxtLog.append(qProcessChild.readAllStandardOutput());
});
QObject::connect(&qProcessChild, &QProcess::readyReadStandardError,
[&]() {
qTxtLog.append(qProcessChild.readAllStandardError());
});
// run application
return app.exec();
}
I compiled and tested this with VS2013 and Qt 5.9.2 on Windows 10 (64 bit).
To illustrate the test session I wrote a QMake project afterwards testQProcessIO.pro
:
SOURCES = testQProcessIO.cc
QT += widgets
and compiled and tested on cygwin again:
$ g++ -std=c++11 -o testQProcessIOChild testQProcessIOChild.cc
$ ./testQProcessIOChild
Initial number of bottles (-1 ... finish): -1
$ qmake-qt5 testQProcessIO.pro
$ make
g++ -c -fno-keep-inline-dllexport -D_GNU_SOURCE -pipe -O2 -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I. -isystem /usr/include/qt5 -isystem /usr/include/qt5/QtWidgets -isystem /usr/include/qt5/QtGui -isystem /usr/include/qt5/QtCore -I. -I/usr/lib/qt5/mkspecs/cygwin-g++ -o testQProcessIO.o testQProcessIO.cc
g++ -o testQProcessIO.exe testQProcessIO.o -lQt5Widgets -lQt5Gui -lQt5Core -lGL -lpthread
$ ./testQProcessIO
5.9.2
$
Snapshots of my test sessions: