0

In Qt/C++, from a dynamic library (Qt C++ Class Project), I need to spawn a new process for a command line command (dir /s is a good example) without locking up the GUI that calls this library function. Then, I need to peek at this standard output and standard error until the spawned process is completed. How do I spawn an async QProcess from a dynamic library, and then peek at the output until it's done?

The following code doesn't work, but it's got pieces that show the thought process.

QString ctCommand::testCommand()
{
  QObject *parent;
  QProcess *console = new QProcess(parent);
  console->connect(console,SIGNAL(readyReadStandardOutput()),this,SLOT(out()));
  console->setReadChannel(QProcess::StandardOutput);
  console->start("dir /s");
}

QString ctCommand::out()
{
  QByteArray processOutput;
  processOutput = console->readAllStandardOutput();
  return QString(processOutput);
}
Volomike
  • 23,743
  • 21
  • 113
  • 209
  • _dir_ command will not work with `QProcess` according to [documentation](http://doc.qt.io/qt-5/qprocess.html#notes-for-windows-users) – Alexander Sorokin Sep 15 '15 at 20:52
  • "The following code doesn't work" Doesn't work **how**? You must check error conditions and provide some debug output to let you know what's going on. Once you do, it'll become obvious what the problem is. – Kuba hasn't forgotten Monica Sep 15 '15 at 21:04
  • @AlexanderSorokin I could use the `cmd.exe /c` technique. However, that was just my example. I'm actually running this on a Mac, building a front-end to a third-party tool that only comes in command line format unfortunately. – Volomike Sep 15 '15 at 21:09
  • @KubaOber It doesn't work in multiple ways. First, how do I make a private variable accessible to all the functions of this class -- in particular the console variable. Second, some examples on the web show the `new QProcess(parent)` technique where parent is a QObject. Others, however, use `this` but run from a GUI application, which isn't the case here. Third, I'm getting an error "no matching member function call for call to 'connect'" on the `console->connect()` line. – Volomike Sep 15 '15 at 21:12
  • "how do I make a private variable accessible to all the functions of this class" Make it a class member. Make it simply a `QProcess`, not `QProcess*`. The pointer fetish in Qt code sometimes reaches the absurd. If you wish to spawn multiple processes, then certainly a dynamically allocated process is OK. Simply connect its output to a functor that captures the process pointer. – Kuba hasn't forgotten Monica Sep 15 '15 at 21:14

1 Answers1

4

You need to connect signals from object started from library to your main class object.

Library

process.h

#include <QtCore/qglobal.h>
#include <QObject>
#include <QProcess>

#if defined(PROCESS_LIBRARY)
#  define PROCESSSHARED_EXPORT Q_DECL_EXPORT
#else
#  define PROCESSSHARED_EXPORT Q_DECL_IMPORT
#endif

class PROCESSSHARED_EXPORT Process : public QObject
{
     Q_OBJECT
public:
    Process(const QString &p_Command, QObject *p_Parent = nullptr);
    QString getCommand() const;

signals:
    void readyRead(QByteArray);

public slots:
    void runCommand(const QString &p_Command);
    void runCommand();
    void setCommand(const QString &p_Command);

private slots:
    void out();
    void processFinished(int p_Code);

private:
    QString command;
};

process.cpp

#include "process.h"

Process::Process(const QString &p_Command, QObject *p_Parent)
    : QObject(p_Parent)
{
    command = p_Command;
}

void Process::runCommand(const QString &p_Command) {
    command = p_Command;
    runCommand();
}

void Process::runCommand() {
    QProcess *console = new QProcess(this);
    console->connect(console, SIGNAL(readyRead()),
        this, SLOT(out()));
    console->connect(console, SIGNAL(finished(int)),
        this, SLOT(processFinished(int)));
    console->start(command);
}

void Process::out() {
    QProcess *console = qobject_cast<QProcess*>(QObject::sender());
    QByteArray processOutput = console->readAll();
    emit readyRead(processOutput);
}

void Process::processFinished(int p_Code) {
    QProcess *console = qobject_cast<QProcess*>(QObject::sender());
    QByteArray processOutput = console->readAll()
        + QString("Finished with code %1").arg(p_Code).toLatin1();
    emit readyRead(processOutput);
}

QString Process::getCommand() const {
    return command;
}

void Process::setCommand(const QString &p_Command) {
    command = p_Command;
}

Usage

void MainWindow::showCustomMessage()
{
    Process *tempProcess = new Process("ping google.com", this);
    connect(tempProcess, SIGNAL(readyRead(QByteArray)),
        this, SLOT(processResponded(QByteArray)));
    tempProcess->runCommand();
}

void MainWindow::processResponded(QByteArray p_Data) {
    qDebug() << p_Data;
}

If someone have questions about creating libraries in Qt - visit official Wiki

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • Do you need to be creating new processes each time you start the command? If you insist on lazily constructing the process only when it's needed, you can certainly do that, but a new process each time is unnecessary. – Kuba hasn't forgotten Monica Sep 15 '15 at 21:18
  • It is possible to reuse `Process`. Adding `void finished(int);` to `signals` section of `Process` class and emitting it from `Process::processFinished(int p_Code)` will make it easier. Then you will need to listen to `finished(int)` signal from main class. After that call `Process::runCommand(QString)` passing new command as parameter. – Alexander Sorokin Sep 15 '15 at 21:23
  • A C++ colleague suggested I should also consider using QtConcurrent API. It might make this code simpler? http://doc.qt.io/qt-5/qtconcurrent-index.html – Volomike Sep 16 '15 at 17:09
  • @Volomike The concurrent API has got nothing to do with processes. This code is really as simple as it gets. – Kuba hasn't forgotten Monica Sep 18 '15 at 22:16
  • This code allows creation of multiple processes, but it mingles all of their outputs together. That's not really desirable. – Kuba hasn't forgotten Monica Sep 18 '15 at 22:20