二重起動の防止(1)

今回はアプリケーションの二重起動の防止をやってみたいと思います。と言っても、今回やるのは、起動されていたら単にメッセージを表示して終了するだけの、それほど高度ではないものですが。

自分自身がすでに起動しているかどうかを知るためには、一般的にミューテックスオブジェクトというカーネルオブジェクトが使われるようです。ということで、まずはミューテックスオブジェクトの説明から。

ミューテックスオブジェクト

Windows ではいろいろなものが『オブジェクト』というものとして扱われていて、それらは大別してカーネルオブジェクトGDIオブジェクトユーザーオブジェクトの3つがあるということは、以前に説明したと思います。

また、そのカーネルオブジェクトの中には、スレッド間の同期を取ることを目的としたオブジェクト(同期オブジェクト)があり、それら同期オブジェクトは『シグナル状態』と『非シグナル状態』という2つの状態があることも述べましたよね。

Windowsオブジェクトについては、『共有メモリを使ってみる』の項を、また、同期オブジェクトについては『サブプロセスの終了を待つ』の項を参照してください。

Windowsの同期オブジェクトの1つに、『ミューテックス(mutex)』というものがあります。ミューテックスオブジェクトは、一言で言えば、「ある1つのスレッドに所有されている状態」と、「どのスレッドにも所有されていない状態」の2つの状態を持つオブジェクトです。この2つのうち、「所有されている状態」が非シグナル状態であり、「所有されていない状態」がシグナル状態となります。


さて、上のように難しい説明してきましたが、残念ながら今回は、ミューテックスオブジェクトで最も重要である『被所有者』としての機能は、ここではまったく使われません。本来なら、この『被所有者』としての機能と『待機関数』を組み合わせることで、複数のスレッド間でデータアクセスの同期を取るために使われるのですが。

今回は、「システム中にただ1つだけ存在する」という、名前付きカーネルオブジェクトの特性を利用することになります。プロセス間共有メモリで使用したファイルマッピングオブジェクトのときと同様に、ミューテックスオブジェクトには任意の名前をつけることができます。そして、その名前を指定すれば、他のプロセスから同一のオブジェクトを扱うことができるのです。この特徴のおかげで、すでに同じアプリケーションが起動しているかどうかを、指定された名前(そのアプリケーション特有の名前)のミューテックスオブジェクトがすでに存在するかどうかということから知ることができるのです。

このように考えてくると、「ミューテックスオブジェクトでなくて、他の名前付きカーネルオブジェクト(例えばファイルマッピングオブジェクト)を使ってもいいんじゃないか?」などと考えることでしょう。実はその通りで、ミューテックスオブジェクトでなくてもいいのです。でも、一般的にはミューテックスオブジェクトが最も多く使われているようです。これは、ミューテックスオブジェクトは「どのスレッドに所有されているか(またはどのスレッドにも管理されていないか)」のみを管理すればいいので、他のカーネルオブジェクトと比べてオブジェクト管理のために消費されるメモリが少なくて済むためではないかと考えられます。

手順

それでは、ミューテックスオブジェクトを使って、自分自身がすでに起動しているかどうかを取得する手順を説明していくことにしましょう。

アプリケーションは、起動したらまずアプリケーション特有の名前をつけたミューテックスオブジェクトを作成します。その際に、すでに同じ名前のミューテックスが存在していれば、もうすでにアプリケーションが起動していることになります。逆に、ミューテックスを作成したときに同じ名前のミューテックスがまだ存在していなければ、それが最初に起動されたものだということになります。

新しいミューテックスを作成するにはCreateMutex関数を使います。

HANDLE CreateMutexA(
    PSECURITY_ATTRIBUTES  psa,    // セキュリティ指定子.
    BOOL    bInitialOwner,        // 所有権指定フラグ.
    PCTSTR  pszMutexName          // オブジェクトの名前.
);

第1引数(psaパラメータ)にはセキュリティ関係の構造体のポインタを指定するのですが、通常は0 (NULL) を指定しておけば問題ありません。

第2引数(bInitialOwnerパラメータ)には、呼び出したスレッドがミューテックスの所有権を強制的に取得するかどうかを指定します。所有権を取得するのなら1 (TRUE) を、所有権を取得しないのなら0 (FALSE) を指定します。しかし、今回はミューテックスの所有権は問題にならないので、どちらでもかまいません。

第3引数(pszMutexNameパラメータ)には、ミューテックスオブジェクトの名前を格納した文字列変数のアドレスを指定します。ミューテックスオブジェクトが、自分のアプリケーションが作成したものかを識別するために、アプリケーション固有の名前を付けます。

この関数が成功すると、ミューテックスオブジェクトのハンドルが返ります。このハンドルは、作成されたミューテックスオブジェクトを識別するために必要になるものです。また、作成に失敗した場合には0 (NULL) が戻り値として返されます。


さて、pszMutexNameパラメータで指定された名前のミューテックスオブジェクトがすでに存在しているかどうかを取得するには、CreateMutex関数を実行した直後にGetLastError関数を呼び出します。もしもこの関数の戻り値が183 (ERROR_ALREADY_EXISTS) ならば、すでに同じ名前のミューテックスオブジェクトが存在していたことになります。これは、ファイルマッピングオブジェクトのときとまったく同じです。

ミューテックスオブジェクトは、不要になったらCloseHandle関数でハンドルをクローズします。カーネルオブジェクトのハンドルはプロセス終了時に自動的にクローズされることになっているので、今回のような使い方の場合には、必ずしもしなければならないというわけではありませんが、クローズするように心がけましょう。

サンプルスクリプト

実際にスクリプトを作成してみましょう。

今回はモジュールを使ってみます。モジュール内で定義されているAlreadyAppRunning関数は、固有の名前を持つミューテックスオブジェクトを作成し、すでにオブジェクトが存在した場合には1を、存在しなかった場合には0を返しています。また、同様にモジュール内で定義されているCleanupAppRunChecker命令は、ミューテックスオブジェクトのハンドルをクローズします。このCleanupAppRunChecker命令は、プログラム終了時に自動的に実行されるようにonexitを指定しています。

#module ;---------アプリケーションの起動チェックを行うモジュール----------

#uselib "kernel32.dll"
#func  CloseHandle  "CloseHandle"  int
#cfunc CreateMutex  "CreateMutexA" int, int, sptr
#cfunc GetLastError "GetLastError"

#define ERROR_ALREADY_EXISTS    183

; ミューテックスオブジェクトの名前の定義
; (アプリケーション固有の名前にする必要があります)
#define MUTEX_NAME  "HSP_WinAPI_Test_Mutex"

; このアプリケーションがすでに起動されているかどうかを取得する関数
#defcfunc AlreadyAppRunning

if (hMutex == 0) {
    ; 名前付きミューテックスオブジェクトの作成
    hMutex = CreateMutex(0, 0, MUTEX_NAME)

    ; オブジェクトがすでに作成されていたかどうかの判別
    if (GetLastError() == ERROR_ALREADY_EXISTS) {
        ; すでに同じ名前のオブジェクトが存在する
        alreadyRunning = 1
    } else {
        ; オブジェクトが新しく作成された
        alreadyRunning = 0
    }
}
return alreadyRunning

; クリーンアップ処理(終了時に自動実行)
#deffunc CleanupAppRunChecker onexit
if (hMutex != 0) {
    ; ミューテックスオブジェクトハンドルのクローズ
    CloseHandle hMutex
    hMutex = 0
}
return

#global ;------------------------モジュール終わり-------------------------


; アプリケーションの実行チェック
if (AlreadyAppRunning()) {
    dialog "すでに起動されています。"
    end
}
stop

上のスクリプトを実行させると、2度目以降に実行したものはメッセージボックスが出て終了します。ちなみに、起動している状態で、ミューテックスオブジェクトの名前(モジュール空間中で定数名MUTEX_NAMEで定義されている名前)を変えて実行させると、別のミューテックスオブジェクトが新しく作成されるので、ダイアログが出ません。


今回はすでに起動されているかどうかを知ることしかできませんでしたので、次回はもっと高度な手法による実用的な二重起動防止処理をやってみます。