17.1.  QProcess and Process Control

[ fromfile: qprocess.xml id: qprocess ]

QProcess is a convenient (and cross-platform) class for starting and controlling other processes. It is derived from QObject and takes full advantage of signals and slots to make it easier to "hook up" with other Qt classes.

Now consider a simple example that starts a process and views its continually running output.[98] Example 17.1 shows the definition of a simple class derived from QProcess.

Example 17.1. src/logtail/logtail.h

[ . . . . ]
#include <QObject>
#include <QProcess>
class LogTail : public QProcess {
    Q_OBJECT
  public:
    LogTail(QString fn = QString());
    ~LogTail();
  signals:
    void logString(const QString &str);
    
  public slots:
    void logOutput();
};
[ . . . . ]

<include src="src/logtail/logtail.h" href="src/logtail/logtail.h" id="logtailh" mode="cpp"/>


A QProcess can launch[99] another process using the start() function. The new process is a child process that terminates when the parent process does.[100] Example 17.2 shows the implementation of the constructor and destructor of the LogTail class.

Example 17.2. src/logtail/logtail.cpp

[ . . . . ]

LogTail::LogTail(QString fn) {
    connect (this, SIGNAL(readyReadStandardOutput()), 
        this, SLOT(logOutput()));       1
    QStringList argv;
     
    argv << "-f" << fn;                 2 
    start("tail", argv);                3
}
LogTail::~LogTail() {
    terminate();                        4
}

1

When there is input ready, call this slot.

2

tail -f filename

3

Returns immediately, and now there is a child process running, "attached" to this process. When this process exits, the child tail process will also terminate.

4

Attempts to terminate this process.

<include src="src/logtail/logtail.cpp" mode="cpp" href="src/logtail/logtail.cpp" id="logtailctor" segid="constructor"/>


The child process can be treated as a sequential I/O device with two predefined output channels that represent two separate streams of data: stdout and stderr. The parent process can select an output channel with setReadChannel() (default is stdout). The signal readyReadStandardOutput() is emitted when data is available on the selected channel of the child process. The parent process can then read its output by calling read(), readLine(), or getChar(). If the child process has standard input enabled, the parent can use write() to send data to it.

Example 17.3 shows the implementation of the slot logOutput() which is connected to the signal readyReadStandardOutput() and uses readAllStandardOutut() so that it pays attention only to stdout.

Example 17.3. src/logtail/logtail.cpp

[ . . . . ]

// tail sends its output to stdout.
void LogTail::logOutput() {             1
    QByteArray bytes = readAllStandardOutput();
    QStringList lines = QString(bytes).split("\n");
    foreach (QString line, lines) {
        emit logString(line); 
    }
}

1

Slot called whenever there is input to read.

<include src="src/logtail/logtail.cpp" mode="cpp" href="src/logtail/logtail.cpp" id="logoutput" segid="logOutput"/>


The use of signals eliminates the need for a read loop. When there is no more input to be read, the slot will no longer be called. Signals and slots make concurrent code much simpler to read, because they hide the event-handling and dispatching code. Some client code is given in Example 17.4.

Example 17.4. src/logtail/logtail.cpp

[ . . . . ]

int main (int argc, char* argv[]) {
    QApplication app(argc, argv);
    QStringList al = app.arguments(); 
    QTextEdit textEdit;
    textEdit.setWindowTitle("Debug");
    textEdit.setWindowTitle("logtail demo");
    QString filename;
    if (al.size() > 1) filename = al[1];
    LogTail tail(filename);             1
    tail.connect (&tail, SIGNAL(logString(const QString&)),
        &textEdit, SLOT(append(const QString&)));
    textEdit.show();
    return app.exec();
}

1

Create object, starts process too.

<include src="src/logtail/logtail.cpp" mode="cpp" href="src/logtail/logtail.cpp" id="logtailclient" segid="main"/>


This application appends lines to the QTextEdit whenever they appear in the specified log file. To demonstrate the LogTail application you need a text file that constantly grows as lines are added to it; for example, some kind of active log file. If you can't find one, you can create one using a tool such as top, a utility available on a typical *nix host. Normally top, with no command-line arguments, produces a plain-text, formatted screen that lists, in descending order of resource usage, the 25 running processes that use the most system resources at the moment. The display starts with a summary of system usage specs and the entire display is updated every few seconds. top continues until it is terminated by the user. In this example, we launch top with command-line arguments:

After that, run the logtail example on the result file:

  top -b -d 1.0 > toplog &
  ./logtail toplog
  

Figure 17.1 is a screenshot of the running program.

Figure 17.1. LogTail in use

LogTail in use

The demo runs until terminated, after which you must also kill the top job, whose [job number] and process id were displayed just after it was launched. Using bash, you need only the job number (%1) to kill the job.

src/logtail> top -b -d 1.0 > toplog &
[1] 24209
src/logtail> ./logtail toplog
      [[ logtail was terminated here. ]]
QProcess: Destroyed while process is still running.
src/logtail> kill 24209
src/logtail>
  



[98] tail -f runs forever showing whatever is appended to a file and is useful for showing the contents of a log file of a running process.

[99] Underscoring the value of the cross-platform QProcess API is the fact that the mechanism for one process to launch another differs considerably in the two leading operating system families. For more information about how it is handled in *nix systems, see the Wikipedia article Fork. The Microsoft Windows approach is described in Spawn.

[100] It is also possible to use startDetached() to start a process that can live after the parent process dies.