Qtの基礎 - プロセス
概要
QProcessクラスは、外部プロセスを起動して、それらと通信するためのQtのクラスである。
外部プログラムの実行、入出力の制御、プロセスの状態監視等を行うことができる。
QProcessクラスはQIODeviceクラスを継承しているため、標準的なQt入出力機能を利用できる。
これにより、readメソッドやwriteメソッド等の操作が可能であり、外部プロセスとのデータのやり取りが容易になる。
プロセスの起動方法は、startメソッドを使用する。
QProcess process;
process.start("notepad.exe", QStringList());
プロセスの実行状態は、様々なシグナルを通じて監視することができる。
主要なシグナルとしては、プロセスが開始された時のstartedメソッド、終了した時のfinishedメソッド、エラーが発生した時のerrorOccurredメソッド等がある。
標準入出力とエラー出力の制御も可能である。
setStandardOutputProcessメソッドを使用して、あるプロセスの標準出力を別のプロセスの標準入力にパイプすることができる。
また、setProcessChannelModeメソッドを使用して、標準出力とエラー出力の扱い方を制御することも可能である。
エラーハンドリングでは、error
メソッドを使用してエラーの種類を確認することができる。
一般的なエラーとしては、プロセスが見つからない、アクセス権限がない、タイムアウト等がある。
プロセスの終了制御も柔軟であり、通常の終了要求であるterminate
メソッドや強制終了のkill
メソッドが用意されている。
また、waitForFinished
メソッドを使用して、プロセスの終了を待機することもできる。
環境変数の制御も可能で、setEnvironment
メソッドやsetWorkingDirectory
メソッドを使用して、プロセスの実行環境をカスタマイズできる。
QProcess::ExitStatus列挙体
指定可能な値 | 説明 |
---|---|
QProcess::NormalExit |
|
QProcess::CrashExit |
|
これらの状態を監視することにより、外部プロセスの終了の状態 (正常あるいは異常) を判断して、適切なエラーハンドリングを実装することができる。
// 使用例
QObject::connect(process, &QProcess::finished, [=](int exitCode, QProcess::ExitStatus exitStatus) {
if (exitStatus == QProcess::NormalExit) {
// プロセスが正常に終了した場合
if (exitCode == 0) qDebug() << "プロセスは正常に終了しました";
else qDebug() << "プロセスはエラーコード" << exitCode << "で終了しました";
}
else {
// QProcess::CrashExit (プロセスがクラッシュした場合)
qDebug() << "プロセスがクラッシュしました";
}
});
プロセスの非同期実行
プロセスを非同期で実行する場合はQProcess::start
メソッド、同期して実行する場合はQProcess.execute
メソッドを使用する。
また、非同期で実行する場合、標準出力や標準エラー出力を取得してウィンドウに表示することもできる。
以下の例は、プロセスを非同期で実行して、プロセスの標準出力と標準エラー出力をウィンドウに表示している。
// MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QProcess>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
private:
QProcess m_proc;
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
// プロセス用のスロット
void ProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);
void UpdateOutput();
void UpdateError();
};
#endif // MAINWINDOW_H
// MainWindow.cpp
#include <QApplication>
#include <QProcess>
#include "Mainwindow.h"
#include "ui_Mainwindow.h"
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this);
// プロセス用のシグナルとスロットを接続する
// Qt 4の記述方法
//QObject::connect(&m_proc, SIGNAL(readyReadStandardOutput()), this, SLOT(UpdateOutput()));
//QObject::connect(&m_proc, SIGNAL(readyReadStandardError()), this, SLOT(UpdateError()));
//QObject::connect(&m_proc, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(ProcessFinished(int, QProcess::ExitStatus)));
// Qt 5の規約
QObject::connect(&m_proc, &QProcess::readyReadStandardOutput, this, &MainWindow::UpdateOutput);
QObject::connect(&m_proc, &QProcess::readyReadStandardError, this, &MainWindow::UpdateError);
QObject::connect(&m_proc, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, &MainWindow::ProcessFinished);
// Qt 5 (C++14以降)
//QObject::connect(&m_proc, qOverload<int, QProcess::ExitStatus >(&QProcess::finished), this, &MainWindow::ProcessFinished);
}
MainWindow::~MainWindow()
{
delete ui;
}
// スロット : 標準出力を表示する
void MainWindow::UpdateOutput()
{
// 標準出力を取得して文字列にする
QByteArray output = m_proc.readAllStandardOutput();
QString str = QString::fromLocal8Bit(output);
}
// スロット : 標準エラー出力を表示する
void MainWindow::UpdateError()
{
// 標準エラー出力を取得して文字列にする
QByteArray output = m_proc.readAllStandardError();
QString str = QString::fromLocal8Bit(output);
}
// スロット : プロセス終了時
void MainWindow::ProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
if(exitStatus == QProcess::CrashExit)
{
QMessageBox::warning(this, tr("Error"), tr("Crashed"));
}
else if(exitCode != 0)
{
QMessageBox::warning(this, tr("Error"), tr("Failed"));
}
else
{ // 正常終了時の処理
}
}
// プロセスの起動
void MainWindow::ProcessStart()
{
QStringList listArgs;
listArgs << "<引数1>" << "<引数2>";
m_proc.start("<実行するプログラムのパス>", listArgs);
}
また、QProcess
クラスのwaitForFinished
メソッドは、プロセスが終了するまで待機するメソッドである。
プロセスが正常に終了した場合はtrue
、そうでない場合(操作のタイムアウト、エラーの発生、既にQProcess
のオブジェクトが終了している)はfalse
となる。
QProcess proc;
proc.start("/usr/bin/sh" args);
bool bStatus = proc.waitForFinished(10 * 1000); // 最大10[s]待機
if (m_proc.exitStatus() != QProcess::NormalExit || proc.exitCode() != 0)
{
std::fprintf(stderr, "error : %s", proc.readAllStandardError().constData());
return -1;
}
プロセスの同期実行
以下の例では、which
コマンドを実行して、whoami
コマンドが存在しているかどうかを確認している。
#include <QProcess>
QProcess process;
process.start("which", QStringList() << "whoami");
process.waitForFinished();
// process.exitCode()メソッドの戻り値が0の場合は、whoamiコマンドが存在している
// 0以外の戻り値の場合は、whoamiコマンドは存在していない
auto iRet = process.exitCode();
以下の例では、外部コマンドとしてwhoami
コマンドを実行して、その結果であるユーザ名を取得している。
#include <QProcess>
// 現在のユーザ名を取得
QProcess process;
process.start("whoami");
process.waitForFinished();
// process.exitCode()メソッドの戻り値が0の場合は、whoamiコマンドが正常に実行されている
// 0以外の戻り値の場合は、whoamiコマンドの実行に失敗している
if (process.exitCode() == 0) {
QString currentUser = QString(process.readAllStandardOutput()).trimmed();
}
余談ではあるが、Linuxシステムでは、C++の標準ライブラリとPOSIX APIを使用してユーザ名を直接取得することができる。
#include <unistd.h>
#include <pwd.h>
uid_t uid = geteuid();
struct passwd *pw = getpwuid(uid);
if (pw) {
auto str = QString::fromLocal8Bit(pw->pw_name);
}
このアプロ―チの利点として、以下に示すものが挙げられる。
- 外部コマンドに依存しないため、whoamiコマンドが利用できないシステムでも動作する。
- プロセスの起動オーバーヘッドがないため、より高速である。
- セキュリティの観点からも、外部コマンドを実行するよりも安全である。
※注意
- この方法は、POSIX準拠のシステム (Linuxを含む多くのUnixライクシステム) でのみ動作するため、Windowsでは異なるアプローチが必要となる。
- unistd.hファイルとpwd.hファイルのヘッダファイルを含める必要がある。
これらは標準的なPOSIXヘッダであるが、クロスプラットフォーム開発の場合は注意が必要である。
切り離されたプロセス
QProcess::startDetachedメソッド
Qt 5.10以降、QProcess
クラスを使用して、プロセスを切り離して実行できる。
start
メソッドやexecute
メソッドでは、プロセスを切り離さずに実行するため、QProcess
クラスのデストラクタがプロセスを終了する。
これに対して、切り離されたプロセスは、呼び出したプロセスが終了しても影響を受けずに実行し続ける。
Linuxでは、切り離されたプロセスは独自のセッションで実行されて、デーモンのように動作する。
切り離されたプロセスを実行するには、QProcess::startDetached
メソッドを使用する。
なお、QProcess
クラスのstartDetached
メソッドは、静的メソッドであることに注意する。
以下の例では、aplay
コマンドを使用して、Sample.wavを再生している。
QProcess::startDetached("aplay Sample.wav");
通常のプロセスと同様に、複数の引数と作業ディレクトリを渡すことができる。
また、開始されたプロセスのPID
を取得することができる。
// プロセスの開始
qint64 pid;
QProcess::startDetached("mpg123", {"Sample.mp3", <引数2>, <引数3>, ...}, musicDirPath, &pid);
// ...略
QProcess::startDetached
メソッドで取得したPID
を使用して、プロセスをkill
することもできる。
// プロセスの終了
QProcess::startDetached("kill", {QString::number(pid)});
切り離されたプロセスを実行するにあたり、プロセスの出力を制御することもできる。
(使用するコマンドの中には、--quiet
オプションが存在するものもある)
- プロセス環境を設定する。
stdin
、stdout
、stderr
をファイルにリダイレクトする。- ネイティブな引数を設定、および、切り離されたプロセス用の
CreateProcess
関数(Windows API)の引数の設定
ただし、static
なstartDetached
メソッドではなく、非static
なQProcess
クラスのstartDetached
メソッドを使用する必要がある。
これは、QProcess
オブジェクトのプロパティを読み込み、それに応じて切り離されたプロセスを開始する。
以下の例では、mpg123
コマンドを使用して、Sample.mp3を再生している。
/dev/null
へのリダイレクトは、ユーザがオーディオプレーヤの視覚的な出力を非表示にしている。
QProcess process;
process.setProgram("mpg123");
process.setArguments({"Sample.mp3"});
process.setWorkingDirectory(musicDirPath); // 例. ファイルが存在するディレクトリのパス(/home/hpge/music等)
process.setStandardOutputFile(QProcess::nullDevice()); // /dev/nullへのリダイレクト
process.setStandardErrorFile(QProcess::nullDevice()); // /dev/nullへのリダイレクト
qint64 pid;
process.startDetached(&pid);
// ...略
以下に、startDetached
メソッドでサポートされているサブセットを示す。
サブセットを使用する場合、QProcess
クラスの他の全てのプロパティは無視されることに注意すること。
setArguments
メソッドsetCreateProcessArgumentsModifier
メソッドsetNativeArguments
メソッドsetProcessEnvironment
メソッドsetProgram
メソッドsetStandardErrorFile
メソッドsetStandardInputFile
メソッドsetStandardOutputFile
メソッドsetWorkingDirectory
メソッド
プロセスの完了を検知
QProcess::startDetached
メソッドを使用した場合、プロセスの完了通知を直接受け取ることはできない。
これは、QProcess::startDetached
メソッドはデタッチ (切り離し) されたプロセスを起動するため、親プロセス (アプリケーション) との接続が切られるためである。
そのため、QProcess::startDetachedメソッドは使用せずに、QProcessクラスのstartメソッドをバッググラウンドで実行する必要がある。
// プロセスを定義
QProcess *process = new QProcess();
process->setProgram("aplay");
process->setArguments(QStringList() << "Sample.wav");
// プロセスを非同期実行 (バックグラウンド) で実行しつつ、完了通知を受け取る
QObject::connect(process, &QProcess::finished, [=](int exitCode, QProcess::ExitStatus exitStatus) {
process->deleteLater(); // メモリリーク防止
// 何らかの処理
// ...略
});
process->start();
// クラス内でシグナル / スロットを定義する場合 (ラムダ式の部分をクラスのメソッドとして記述することも可能)
// プロセスを定義
m_Process = new QProcess(this);
Process->setProgram("aplay");
m_Process->setArguments(QStringList() << "Sample.wav");
connect(m_Process, &QProcess::finished, this, &SomeClass::someMethod);
// SomeClassクラスのsomeMethodメソッド
class someClass : public QObject
{
private:
QProcess *m_Process;
private:
void someMethod(int exitCode, QProcess::ExitStatus exitStatus)
{
m_Process->deleteLater(); // メモリリーク防止
// 何らかの処理
// ...略
});
}