1

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.

Ian
  • 95
  • 13
  • Take a look at QProcess – Sebastian Lange Jul 20 '17 at 06:13
  • Thanks, i think that will suit my issues with the reading/writing channels. – Ian Jul 20 '17 at 06:21
  • From the sound of it, I am not sure QProcess will be adequate for your needs. You have to refactor the console app so that its input source and output destination are abstracted away. In one case, you can use the console for both. In the GUI application case, various elements of the GUI will act as input source and output destination. Hope I am not overthinking your needs. Good luck. – R Sahu Jul 20 '17 at 06:29
  • I'm not sure about Qt but when we did it on Windows (with GTK+ but this was not the issue), we had issues that parent process was blocked until child process was finished. (We solved it by spawing in an extra thread.) We also had issues that std output of child process was not received until child process was finished. (I forgot how we fixed it.) I would enjoy if you could leave a note about this when you solved it for Qt. (Meanwhile we changed to Qt also, and I'm curious about this...) – Scheff's Cat Jul 20 '17 at 06:31
  • I can't change the console application because it's an ready to use application. Actually i only need some kind of virtual console which inputs are triggered by GUI elements. If i find an adequate way to do this, i'll post the solution here! For now i can't handle the in/output of the application. That's the first thing i'll look for. – Ian Jul 20 '17 at 06:57
  • 1
    @Scheff in Qt, QProcess is based on events and is not blocking the parent-process by default. You also have a synchronous API if you want to run it blocking or do not have a Qt eventloop. I think the class can cover all the question needs here with the asynchronous behaviour. – ymoreau Jul 20 '17 at 08:00
  • @ymoreau Yes, I found out just now. (finishing my sample.) I'm impressed... – Scheff's Cat Jul 20 '17 at 08:35

2 Answers2

1

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:

Snapshot of testQProcessIO (build with VS2013, started from Explorer)  Snapshot of testQProcessIO (build with g++, started from cygwin/bash)

Scheff's Cat
  • 16,517
  • 5
  • 25
  • 45
  • Thanks, that looks like a straight forward QWidgets solution! I've posted my approach for QtQuick down below. – Ian Jul 21 '17 at 06:00
1

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.

Ian
  • 95
  • 13