Qtの応用 - 多重起動の禁止
概要
ソフトウェアのインスタンスを1つだけ実行(多重起動を禁止)する理由として、
メモリリークの問題を制限する、または、リソース、ファイル、データベース等をアプリケーションの2つのインスタンスが奪い合うことで起こる問題を排除するためである。
また、原則として、ユーザが使用するアプリケーションのコピーは1つだけでよい場合もある。
多重起動を禁止する方法には、以下に示す2つの方法がある。
- QLockFileの使用
- 一時的なファイルが作成されて、ソフトウェア終了時に破棄する。
- これは、ソフトウェアを多重起動された時、その一時ファイルの存在を確認して、一時ファイルが存在するならばインスタンスを自動的に閉じる。
- QSystemSemaphoreとQSharedMemoryの使用
- 共有メモリセグメントを作成して、一意の識別子を持つ既存のセグメントに接続する。
- 接続に成功した場合、ソフトウェアのインスタンスが既に作成されているため、そのことをユーザに通知してソフトウェアを終了する。
- 接続に失敗した場合、ソフトウェア用のメモリセグメントを作成して、最初のインスタンスを実行する。
QLockFileの使用
ソフトウェアの起動時に作成された一時ファイル(Lock File)の作成に失敗する場合、
既にソフトウェアのインスタンスが存在すると仮定して、ユーザに通知してソフトウェアを終了する。
QLockFileは、ソフトウェアの実行インスタンスの数を制限する。
ただし、QLockFileの使用は、ユーザのパーミッションに問題がある場合は使用できない可能性がある。
※注意
<uniq id>は、任意のIDに置き換えること。
#include <QApplication>
#include <QLockFile>
#include <QDir>
#include <QMessageBox>
#include "CMainWindow.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QLockFile lockFile(QDir::temp().absoluteFilePath("<uniq id>.lock"));
// Trying to close the Lock File, if the attempt is unsuccessful for 100 milliseconds,
// then there is a Lock File already created by another process.
// Therefore, we throw a warning and close the program
if(!lockFile.tryLock(100))
{
QMessageBox msgBox;
msgBox.setIcon(QMessageBox::Warning);
msgBox.setText("The application is already running.\n"
"Allowed to run only one instance of the application.");
msgBox.exec();
return -1;
}
CMainWindow w;
w.show();
return a.exec();
}
QSharedMemoryは、1台のPC上において、同時に作業しているすべてのユーザーに共有されるものである。
これは、システム全体として、ソフトウェアの単一インスタンスの実行を制限する場合等に使用する。
したがって、1人のユーザがソフトウェアを実行する場合、他のユーザは同一のソフトウェアを実行することはできない。
しかし、各プラットフォームによる共有メモリの違いが存在する。
Windowsの場合、共有メモリはプログラムが正常に終了した時および緊急事態が発生した時に解放される。
Linux / Unixの場合、緊急時にクラッシュしたメモリは解放されない。
以下の例では、同一のソフトウェアの複数のインスタンスが同時に起動した場合の問題を解決するために使用される。
セマフォはメータで作成されて、その最大数は1になる。
セマフォを解除する時、ソフトウェアの他の全てのインスタンスは共有メモリにアクセスできないため、完全に所有するリソースの1つのコピーを持つ。
このコピーは、ソフトウェアに対応する共有メモリセグメントの識別子の存在により、ソフトウェアの別のインスタンスの実行をチェックする。
共有メモリセグメントのコピーは、正常に起動する時かつ別のソフトウェアインスタンスの情報が見つからない場合に作成される。
その後、セマフォが下げられて、実行するソフトウェアの別のインスタンスが許可される。
※注意
<uniq id 1>および<uniq id 2>は、任意のIDに置き換えること。
#include <QApplication>
#include <QSystemSemaphore>
#include <QSharedMemory>
#include <QMessageBox>
#include "CMainWindow.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QSystemSemaphore semaphore("<uniq id 1>", 1); // create semaphore
semaphore.acquire(); // Raise the semaphore, barring other instances to work with shared memory
#ifndef Q_OS_WIN32
// in linux / unix shared memory is not freed when the application terminates abnormally,
// so you need to get rid of the garbage
QSharedMemory nix_fix_shared_memory("<uniq id 2>");
if(nix_fix_shared_memory.attach())
{
nix_fix_shared_memory.detach();
}
#endif
QSharedMemory sharedMemory("<uniq id 2>"); // Create a copy of the shared memory
bool is_running; // variable to test the already running application
if(sharedMemory.attach())
{ // We are trying to attach a copy of the shared memory
// To an existing segment
is_running = true; // If successful, it determines that there is already a running instance
}
else
{
sharedMemory.create(1); // Otherwise allocate 1 byte of memory
is_running = false; // And determines that another instance is not running
}
semaphore.release();
// If you already run one instance of the application, then we inform the user about it
// and complete the current instance of the application
if(is_running)
{
QMessageBox msgBox;
msgBox.setIcon(QMessageBox::Warning);
msgBox.setText("The application is already running.\n"
"Allowed to run only one instance of the application.");
msgBox.exec();
return -1;
}
CMainWindow w;
w.show();
return a.exec();
}
QLocalServer / QLocalSocketの使用
処理の流れを以下に示す。
QSharedMemory
を使用して、アプリケーションの単一インスタンスを保証する。QLocalServer
とQLocalSocket
を使用して、既存のインスタンスと新しいインスタンス間で通信を行う。- SingleInstanceクラスのインスタンスを生成して、単一インスタンス制御のロジックをカプセル化する。
- SingleInstanceクラスのtryToRunメソッド実行して、アプリケーションが実行可能かどうかを確認する。
- 既存のインスタンスが検出された場合、sendMessagスロットを使用してそのインスタンスにメッセージを送信する。
ロック向け一時ファイルが、/tmp/MyAppServerファイルが自動的に生成される。 - 既存のインスタンスは、messageReceivedシグナルを通じてメッセージを受信して、適切に対応する。
(例: ウインドウのアクティブ化等)
これにより、アプリケーションの多重起動を効果的に防ぎつつ、既存のインスタンスとの通信も可能となる。
既存のインスタンスと通信する機能により、より柔軟で拡張性の高い設計になっている。
必要に応じて、messageReceivedシグナルのハンドラを拡張して、既存のウインドウをアクティブにする等の処理を追加する。
QLocalServer
とQLocalSocket
を使用するには、Qt Networkモジュールが必要となる。
- Qtプロジェクトファイル (.pro) を使用する場合
QT += network
- CMakeLists.txtファイルを使用する場合
find_package(Qt6 REQUIRED COMPONENTS Network)
target_link_libraries(SingleInstanceApp PRIVATE
Qt6::Network
)