DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

二重起動を禁止する

ここでは、アプリケーションの二重起動を禁止する方法(アプリケーションが一つしか起動しないようにする方法)を説明します。なおここで紹介しているサンプルは、Windowsフォームアプリケーションを想定しています。

Mutexを使用する方法

通常は、Mutexを使用する方法が一般的です。.NET FrameworkにはMutexクラスが用意されており、これを使用するのがよいでしょう。

Mutexクラスを使用して二重起動を禁止する例を、以下に示します。ここではエントリポイントで二重起動をチェックしています。エントリポイントが分からないという方は、「アプリケーションのエントリポイントを自作する」をご覧ください。

VB.NET
コードを隠すコードを選択
''' <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
C#
コードを隠すコードを選択
/// <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コンストラクタの別のオーバーロードを使って、次のようにもできます。

VB.NET
コードを隠すコードを選択
''' <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
C#
コードを隠すコードを選択
/// <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で解放するように書き換えました。書き換える前のコードは以下のようなものでした。

VB.NET
コードを隠すコードを選択
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
C#
コードを隠すコードを選択
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#?」を参考にして作成しました。

VB.NET
コードを隠すコードを選択
'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)
C#
コードを隠すコードを選択
//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で、Visual Basicアプリケーションモデルを使用する方法

Visual Studio 2005からは、VB.NETを使用していれば、二重起動を禁止したアプリケーションを作成するのは簡単です。しかも、同じアプリケーションが後で起動したことを先に起動したアプリケーションで知ることもできますし、後で起動したアプリケーションのコマンドライン引数を取得することもできます。

以下にその手順を示します。

  1. メニューの「プロジェクト」-「プロパティ」により、プロジェクトのプロパティを表示します。
  2. 「アプリケーション」タブを選択します(デフォルトで選択されています)。
  3. 「アプリケーションフレームワークを有効にする」にチェックを入れ、有効にします。
  4. 「Windowsアプリケーションフレームワークプロパティ」の「単一インスタンスのアプリケーションを作成する」にチェックを入れます。
  5. 以上です。

このようにして作成されたアプリケーションでは、すでにアプリケーションが起動しているときにもう一つ起動させようとすると、はじめに起動しているアプリケーションがアクティブになり(ウィンドウが最小化状態のときは、元のサイズに戻されます)、後で起動されたアプリケーションはすぐに終了し、表示されません。

補足:はじめに起動しているアプリケーションをアクティブにしないようにもできます。この方法は、後述します。

後で起動されたアプリケーションのコマンドライン引数を取得する

My.Application.StartupNextInstanceイベントを使うことにより、アプリケーションが二重起動されたことを知ることができます。さらに、後で起動されたアプリケーションに指定されたコマンドライン引数を取得することもできます。

StartupNextInstanceイベントハンドラは、ApplicationEvents.vbファイルに記述するのが一般的のようです(別の場所でも問題ありません)。ApplicationEvents.vbファイルは、プロジェクトプロパティの「アプリケーション」タブにある「アプリケーションイベントの表示」をクリックすることにより表示されます。

以下にApplicationEvents.vbファイルにStartupNextInstanceイベントハンドラを記述した例を示します。ここでは、後で起動されたアプリケーションのコマンドライン引数を表示しています。さらに、StartupNextInstanceEventArgs.BringToForegroundをFalseにすることにより、はじめに起動されたアプリケーションをアクティブにしないようにしています。

VB.NET
コードを隠すコードを選択
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と同様のことをする」から考えると、次のようなコードですでにアプリケーションが起動しているか調べることができます。ただしこの方法は、プロセス名が同じ別のアプリケーションが存在すると、うまくいかない可能性があります。

VB.NET
コードを隠すコードを選択
'二重起動をチェックする
If Diagnostics.Process.GetProcessesByName( _
    Diagnostics.Process.GetCurrentProcess.ProcessName).Length > 1 Then
    'すでに起動していると判断する
    MessageBox.Show("多重起動はできません。")
End If
C#
コードを隠すコードを選択
//二重起動をチェックする
if (System.Diagnostics.Process.GetProcessesByName(
    System.Diagnostics.Process.GetCurrentProcess().ProcessName).Length > 1)
{
    //すでに起動していると判断する
    MessageBox.Show("多重起動はできません。");
}

アプリケーションの二重起動を防ぐには、このようなチェックをエントリポイント等で行い、すでに起動していると判断したらアプリケーションを終了させるようにします。

この方法により、エントリポイントで二重起動のチェックをする例を以下に示します。エントリポイントについて詳しくは、「アプリケーションのエントリポイントを自作する」をご覧ください。

補足:この方法では、起動してから二重起動をチェックするまでに時間があるため、素早く2つ起動した場合は、1つも起動しない可能性があります。
VB.NET
コードを隠すコードを選択
'エントリポイント
<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
C#
コードを隠すコードを選択
//エントリポイント
[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());
}
  • 履歴:
  • 2004/4/4 「補足」の追加と、「補足」で説明された事項によるMutexを使用したコードの修正。
  • 2007/2/6 Visual Basicアプリケーションモデルを使用する方法を追加。
  • 2007/9/7 すべてのコードにおいてエントリポイントで二重起動をチェックするように変更。
  • 2009/6/9 「現在起動しているプロセスを調べる方法」のVB.NETのコードで、Uboundの代わりにLengthを使うように変更。「Mutexを使用する方法」で最後にReleaseMutexを呼び出すように変更。
  • 2016/3/31 「Mutexを使用する方法」のサンプルを書き直す。「すべてのユーザーに二重起動を禁止する」を追加など。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • イベントハンドラの意味が分からない、C#のコードをそのまま書いても動かないという方は、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。