ここでは、アプリケーションの二重起動を禁止する方法(アプリケーションが一つしか起動しないようにする方法)を説明します。なおここで紹介しているサンプルは、Windowsフォームアプリケーションを想定しています。
通常は、Mutexを使用する方法が一般的です。.NET FrameworkにはMutexクラスが用意されており、これを使用するのがよいでしょう。
Mutexクラスを使用して二重起動を禁止する例を、以下に示します。ここではエントリポイントで二重起動をチェックしています。エントリポイントが分からないという方は、「アプリケーションのエントリポイントを自作する」をご覧ください。
''' <summary> ''' アプリケーションのメイン エントリ ポイントです。 ''' </summary> <STAThread> _ Shared Sub Main() 'Mutex名を決める(必ずアプリケーション固有の文字列に変更すること!) Dim mutexName As String = "MyApplicationName" 'Mutexオブジェクトを作成する Dim mutex As New System.Threading.Mutex(False, mutexName) Dim hasHandle As Boolean = False Try Try 'ミューテックスの所有権を要求する hasHandle = mutex.WaitOne(0, False) '.NET Framework 2.0以降の場合 Catch ex As System.Threading.AbandonedMutexException '別のアプリケーションがミューテックスを解放しないで終了した時 hasHandle = True End Try 'ミューテックスを得られたか調べる If hasHandle = False Then '得られなかった場合は、すでに起動していると判断して終了 MessageBox.Show("多重起動はできません。") Return End If 'はじめからMainメソッドにあったコードを実行 Application.EnableVisualStyles() Application.SetCompatibleTextRenderingDefault(False) Application.Run(New Form1()) Finally If hasHandle Then 'ミューテックスを解放する mutex.ReleaseMutex() End If mutex.Close() End Try End Sub
/// <summary> /// アプリケーションのメイン エントリ ポイントです。 /// </summary> [STAThread] static void Main() { //Mutex名を決める(必ずアプリケーション固有の文字列に変更すること!) string mutexName = "MyApplicationName"; //Mutexオブジェクトを作成する System.Threading.Mutex mutex = new System.Threading.Mutex(false, mutexName); bool hasHandle = false; try { try { //ミューテックスの所有権を要求する hasHandle = mutex.WaitOne(0, false); } //.NET Framework 2.0以降の場合 catch (System.Threading.AbandonedMutexException) { //別のアプリケーションがミューテックスを解放しないで終了した時 hasHandle = true; } //ミューテックスを得られたか調べる if (hasHandle == false) { //得られなかった場合は、すでに起動していると判断して終了 MessageBox.Show("多重起動はできません。"); return; } //はじめからMainメソッドにあったコードを実行 Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); } finally { if (hasHandle) { //ミューテックスを解放する mutex.ReleaseMutex(); } mutex.Close(); } }
または、Mutexコンストラクタの別のオーバーロードを使って、次のようにもできます。
''' <summary> ''' アプリケーションのメイン エントリ ポイントです。 ''' </summary> <STAThread> _ Shared Sub Main() 'Mutex名を決める(必ずアプリケーション固有の文字列に変更すること!) Dim mutexName As String = "MyApplicationName" 'Mutexオブジェクトを作成する Dim createdNew As Boolean Dim mutex As New System.Threading.Mutex(True, mutexName, createdNew) 'ミューテックスの初期所有権が付与されたか調べる If createdNew = False Then 'されなかった場合は、すでに起動していると判断して終了 MessageBox.Show("多重起動はできません。") mutex.Close() Return End If Try 'はじめからMainメソッドにあったコードを実行 Application.EnableVisualStyles() Application.SetCompatibleTextRenderingDefault(False) Application.Run(New Form1()) Finally 'ミューテックスを解放する mutex.ReleaseMutex() mutex.Close() End Try End Sub
/// <summary> /// アプリケーションのメイン エントリ ポイントです。 /// </summary> [STAThread] static void Main() { //Mutex名を決める(必ずアプリケーション固有の文字列に変更すること!) string mutexName = "MyApplicationName"; //Mutexオブジェクトを作成する bool createdNew; System.Threading.Mutex mutex = new System.Threading.Mutex(true, mutexName, out createdNew); //ミューテックスの初期所有権が付与されたか調べる if (createdNew == false) { //されなかった場合は、すでに起動していると判断して終了 MessageBox.Show("多重起動はできません。"); mutex.Close(); return; } try { //はじめからMainメソッドにあったコードを実行 Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); } finally { //ミューテックスを解放する mutex.ReleaseMutex(); mutex.Close(); } }
補足:.NET Framework 2.0からは、スレッドがMutexを解放せずに終了するとMutexは放棄された状態になり、Mutexを取得する次のスレッドでAbandonedMutexExceptionがスローされるようになりました。そのため、上記のMutexを使ったコードでも最後にReleaseMutexで解放するように書き換えました。書き換える前のコードは以下のようなものでした。
Private Shared _mutex As System.Threading.Mutex 'エントリポイント <STAThread()> _ Shared Sub Main() 'Mutexクラスの作成 '"MyName"の部分を適当な文字列に変えてください _mutex = New System.Threading.Mutex(False, "MyName") 'ミューテックスの所有権を要求する If _mutex.WaitOne(0, False) = False Then 'すでに起動していると判断して終了 MessageBox.Show("多重起動はできません。") Return End If Application.Run(New Form1()) End Sub
private static System.Threading.Mutex _mutex; //エントリポイント [STAThread] static void Main() { //Mutexクラスの作成 //"MyName"の部分を適当な文字列に変えてください _mutex = new System.Threading.Mutex(false, "MyName"); //ミューテックスの所有権を要求する if (_mutex.WaitOne(0, false) == false) { //すでに起動していると判断して終了 MessageBox.Show("多重起動はできません。"); return; } Application.Run(new Form1()); }
このコードではMutexを静的フィールドとしていますが、これをローカル変数とした場合、うまく行かないことがあります。これは、ガベージコレクションによってローカル変数が破棄されてしまう可能性があるためです。このことは、「A mutex puzzle; single instance of application」で報告されています。静的フィールドを使う以外に、GC.KeepAliveメソッドを使用する解決法もあります。この場合、Mainメソッドの最後でGC.KeepAliveメソッドを呼び出して、Mutexがガベージコレクションによって破棄されてしまうのを防ぎます。
上の例では、ユーザー(セッション)毎の二重起動を防ぐことができます。しかし、すでに起動しているアプリケーションも、ユーザーを切り替えれば、起動できます。これを防ぐには、Mutex名の先頭に「Global\」を付けて、グローバルミューテックスにします。
グローバルミューテックスでは、別のユーザーがすでにそのMutexを取得していた時、Mutexを作成する時に例外(.NET Framework 1.1以前ではApplicationException、それ以降ではUnauthorizedAccessException?)がスローされる可能性があります。これを防ぐには、.NET Framework 2.0から追加された、MutexSecurityを指定できるMutexコンストラクタを使用します。
以下にその例(Mutexを作成する部分のみ)を示します。この例は、「What is a good pattern for using a Global Mutex in C#?」を参考にして作成しました。
'Mutex名を決める(必ずアプリケーション固有の文字列に変更すること!) Dim mutexName As String = "MyApplicationName" 'Mutex名の先頭に「Global\」を付けて、Global Mutexにする mutexName = "Global\" & mutexName 'すべてのユーザーにフルコントロールを許可するMutexSecurityを作成する Dim rule As New System.Security.AccessControl.MutexAccessRule( _ New System.Security.Principal.SecurityIdentifier( _ System.Security.Principal.WellKnownSidType.WorldSid, Nothing), _ System.Security.AccessControl.MutexRights.FullControl, _ System.Security.AccessControl.AccessControlType.Allow) Dim mutexSecurity As New System.Security.AccessControl.MutexSecurity() mutexSecurity.AddAccessRule(rule) 'Mutexオブジェクトを作成する Dim createdNew As Boolean Dim mutex As New System.Threading.Mutex(False, mutexName, createdNew, mutexSecurity)
//Mutex名を決める(必ずアプリケーション固有の文字列に変更すること!) string mutexName = "MyApplicationName"; //Mutex名の先頭に「Global\」を付けて、Global Mutexにする mutexName = "Global\\" + mutexName; //すべてのユーザーにフルコントロールを許可するMutexSecurityを作成する System.Security.AccessControl.MutexAccessRule rule = new System.Security.AccessControl.MutexAccessRule( new System.Security.Principal.SecurityIdentifier( System.Security.Principal.WellKnownSidType.WorldSid, null), System.Security.AccessControl.MutexRights.FullControl, System.Security.AccessControl.AccessControlType.Allow); System.Security.AccessControl.MutexSecurity mutexSecurity = new System.Security.AccessControl.MutexSecurity(); mutexSecurity.AddAccessRule(rule); //Mutexオブジェクトを作成する bool createdNew; System.Threading.Mutex mutex = new System.Threading.Mutex(false, mutexName, out createdNew, mutexSecurity);
Visual Studio 2005からは、VB.NETを使用していれば、二重起動を禁止したアプリケーションを作成するのは簡単です。しかも、同じアプリケーションが後で起動したことを先に起動したアプリケーションで知ることもできますし、後で起動したアプリケーションのコマンドライン引数を取得することもできます。
以下にその手順を示します。
このようにして作成されたアプリケーションでは、すでにアプリケーションが起動しているときにもう一つ起動させようとすると、はじめに起動しているアプリケーションがアクティブになり(ウィンドウが最小化状態のときは、元のサイズに戻されます)、後で起動されたアプリケーションはすぐに終了し、表示されません。
補足:はじめに起動しているアプリケーションをアクティブにしないようにもできます。この方法は、後述します。
My.Application.StartupNextInstanceイベントを使うことにより、アプリケーションが二重起動されたことを知ることができます。さらに、後で起動されたアプリケーションに指定されたコマンドライン引数を取得することもできます。
StartupNextInstanceイベントハンドラは、ApplicationEvents.vbファイルに記述するのが一般的のようです(別の場所でも問題ありません)。ApplicationEvents.vbファイルは、プロジェクトプロパティの「アプリケーション」タブにある「アプリケーションイベントの表示」をクリックすることにより表示されます。
以下にApplicationEvents.vbファイルにStartupNextInstanceイベントハンドラを記述した例を示します。ここでは、後で起動されたアプリケーションのコマンドライン引数を表示しています。さらに、StartupNextInstanceEventArgs.BringToForegroundをFalseにすることにより、はじめに起動されたアプリケーションをアクティブにしないようにしています。
Namespace My ' 次のイベントは MyApplication に対して利用できます: ' ' Startup: アプリケーションが開始されたとき、 ' スタートアップ フォームが作成される前に発生します。 ' Shutdown: アプリケーション フォームがすべて閉じられた後に発生します。 ' このイベントは、通常の終了以外の方法でアプリケーションが ' 終了されたときには発生しません。 ' UnhandledException: ハンドルされていない例外がアプリケーションで ' 発生したときに発生するイベントです。 ' StartupNextInstance: 単一インスタンス アプリケーションが起動され、 ' それが既にアクティブであるときに発生します。 ' NetworkAvailabilityChanged: ネットワーク接続が接続されたとき、 ' または切断されたときに発生します。 Partial Friend Class MyApplication Private Sub MyApplication_StartupNextInstance( _ ByVal sender As Object, _ ByVal e As Microsoft.VisualBasic.ApplicationServices. _ StartupNextInstanceEventArgs) _ Handles Me.StartupNextInstance Console.WriteLine("二重起動されました") '後で起動されたアプリケーションのコマンドライン引数を表示 For Each cmd As String In e.CommandLine Console.WriteLine(cmd) Next '先に起動しているアプリケーションをアクティブにしない e.BringToForeground = False End Sub End Class End Namespace
VB6では二重起動を禁止するためにApp.PrevInstanceをチェックする方法が一般的でした。これに習い、「VB6のApp.PrevInstanceと同様のことをする」から考えると、次のようなコードですでにアプリケーションが起動しているか調べることができます。ただしこの方法は、プロセス名が同じ別のアプリケーションが存在すると、うまくいかない可能性があります。
'二重起動をチェックする If Diagnostics.Process.GetProcessesByName( _ Diagnostics.Process.GetCurrentProcess.ProcessName).Length > 1 Then 'すでに起動していると判断する MessageBox.Show("多重起動はできません。") End If
//二重起動をチェックする if (System.Diagnostics.Process.GetProcessesByName( System.Diagnostics.Process.GetCurrentProcess().ProcessName).Length > 1) { //すでに起動していると判断する MessageBox.Show("多重起動はできません。"); }
アプリケーションの二重起動を防ぐには、このようなチェックをエントリポイント等で行い、すでに起動していると判断したらアプリケーションを終了させるようにします。
この方法により、エントリポイントで二重起動のチェックをする例を以下に示します。エントリポイントについて詳しくは、「アプリケーションのエントリポイントを自作する」をご覧ください。
補足:この方法では、起動してから二重起動をチェックするまでに時間があるため、素早く2つ起動した場合は、1つも起動しない可能性があります。
'エントリポイント <STAThread()> _ Shared Sub Main() '二重起動をチェックする If Diagnostics.Process.GetProcessesByName( _ Diagnostics.Process.GetCurrentProcess.ProcessName).Length > 1 Then 'すでに起動していると判断して終了 MessageBox.Show("多重起動はできません。") Return End If 'はじめからMainメソッドにあったコードを実行 Application.EnableVisualStyles() Application.SetCompatibleTextRenderingDefault(False) Application.Run(New Form1()) End Sub
//エントリポイント [STAThread] static void Main() { //二重起動をチェックする if (System.Diagnostics.Process.GetProcessesByName( System.Diagnostics.Process.GetCurrentProcess().ProcessName).Length > 1) { //すでに起動していると判断して終了 MessageBox.Show("多重起動はできません。"); return; } //はじめからMainメソッドにあったコードを実行 Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); }