単一インスタンスのWPFアプリケーションを作成する正しい方法は何ですか?


657

.NETでのC#とWPFの使用(ではなく) Windowsフォームやコンソールて、単一のインスタンスとしてのみ実行できるアプリケーションを作成する正しい方法は何ですか?

私はそれがミューテックスと呼ばれるいくつかの神秘的なものと関係があることを知っていますが、これらの1つが何であるかを停止して説明するのに煩わしい人をめったに見つけることができません。

コードは、ユーザーが2番目のインスタンスを開始しようとしたことをすでに実行中のインスタンスに通知する必要もあり、存在する場合はコマンドライン引数も渡す可能性があります。


14
それでもアプリケーションが終了すると、CLRは解放されていないミューテックスを自動的に解放しませんか?
ココワラ2009年

1
@Cocowalla:ミューテックスがマネージドアプリによって作成されたものか、既存のミューテックスにアタッチされたものかがわからない場合を除き、ファイナライザはアンマネージドミューテックスを破棄する必要があります。
イグナシオソレルガルシア

アプリのインスタンスを1つだけ持つことは合理的です。しかし、既存のアプリに引数を渡すことは、少しばかげているように見えます。理由がわかりません。アプリをファイル拡張子に関連付ける場合は、ユーザーがドキュメントを開くのと同じ数のアプリを開く必要があります。これは、すべてのユーザーが期待する標準的な動作です。
Eric Ouellet、

9
@Cocowalla CLRはネイティブリソースを管理しません。ただし、プロセスが終了すると、すべてのハンドルがシステム(CLRではなくOS)によって解放されます。
IInspectable 2014年

1
私は@huseyintによる答えを好む。Microsoft独自の「SingleInstance.cs」クラスを使用しているため、MutexesやIntPtrsについて心配する必要はありません。また、VisualBasic(yuk)に依存していません。詳細については、codereview.stackexchange.com / questions / 20871 / …を参照してください
Heliac

回答:


537

以下は、Mutexソリューションに関する非常に優れた記事です。この記事で説明されているアプローチは、2つの理由で有利です。

まず、Microsoft.VisualBasicアセンブリへの依存関係は必要ありません。私のプロジェクトが既にそのアセンブリに依存している場合は、おそらく別の回答に示されているアプローチを使用することをお勧めします。しかし、現状では、Microsoft.VisualBasicアセンブリは使用せず、プロジェクトに不要な依存関係を追加しないようにします。

次に、この記事では、ユーザーが別のインスタンスを起動しようとしたときに、アプリケーションの既存のインスタンスをフォアグラウンドにする方法を示しています。これは、ここで説明する他のミューテックスソリューションでは対応していない非常に良いタッチです。


更新

2014年8月1日の時点で、上記にリンクした記事はまだアクティブですが、ブログはしばらく更新されていません。それは私が結局それが消えるかもしれないことを心配させます、そしてそれとともに、提唱された解決策。私は後世のためにここに記事の内容を複製しています。この言葉は、Sanity Free Codingのブログ所有者にのみ帰属します。

今日、私は自分のアプリケーションがそれ自体の複数のインスタンスを実行することを禁止するいくつかのコードをリファクタリングしたいと思いました。

以前は使用していた System.Diagnostics.Processをして、プロセスリストでmyapp.exeのインスタンスを検索していました。これが機能する間、それは多くのオーバーヘッドをもたらします、そして私はよりきれいな何かを望みました。

私はこれにミューテックスを使用できることを知っていました(ただし、これまでに使用したことがない)ので、コードを削減して人生を簡素化することに着手しました。

アプリケーションmainのクラスで、Mutexという名前の静的を作成しました。

static class Program
{
    static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
    [STAThread]
    ...
}

名前付きミューテックスを使用すると、複数のスレッドおよびプロセス間で同期をスタックすることができます。これは、私が探している魔法です。

Mutex.WaitOneには、待機する時間を指定するオーバーロードがあります。実際にコードを同期する必要はないので(さらに、コードが現在使用されているかどうかを確認するだけです)、2つのパラメーターを指定してオーバーロードを使用します:Mutex.WaitOne(Timespan timeout、bool exitContext)。入ることができる場合はtrueを、入ることができない場合はfalseを待機します。この場合、待機する必要はまったくありません。mutexが使用されている場合は、それをスキップして次に進むため、TimeSpan.Zero(0ミリ秒待機)を渡し、exitContextをtrueに設定して、ロックを取得する前に同期コンテキストを終了できるようにします。これを使用して、Application.Runコードを次のようにラップします。

static class Program
{
    static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
    [STAThread]
    static void Main() {
        if(mutex.WaitOne(TimeSpan.Zero, true)) {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
            mutex.ReleaseMutex();
        } else {
            MessageBox.Show("only one instance at a time");
        }
    }
}

したがって、アプリが実行されている場合、WaitOneはfalseを返し、メッセージボックスが表示されます。

メッセージボックスを表示する代わりに、小さなWin32を使用して、実行中のインスタンスに(他のすべてのウィンドウの一番上に移動することによって)既に実行中であることを忘れたことを通知することにしました。これを達成するために、PostMessageを使用してカスタムメッセージをすべてのウィンドウにブロードキャストしました(カスタムメッセージは 、実行中のアプリケーションによってRegisterWindowMessageに登録されました。つまり、私のアプリケーションだけがそれを認識しています)。2番目のインスタンスが終了します。実行中のアプリケーションインスタンスは、その通知を受信して​​処理します。そのために、メインフォームWndProcをオーバーライドし、カスタム通知をリッスンしました。その通知を受け取ったら、フォームのTopMostプロパティをtrueに設定して、フォームを上に表示します。

これが私が終わったものです:

  • Program.cs
static class Program
{
    static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
    [STAThread]
    static void Main() {
        if(mutex.WaitOne(TimeSpan.Zero, true)) {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
            mutex.ReleaseMutex();
        } else {
            // send our Win32 message to make the currently running instance
            // jump on top of all the other windows
            NativeMethods.PostMessage(
                (IntPtr)NativeMethods.HWND_BROADCAST,
                NativeMethods.WM_SHOWME,
                IntPtr.Zero,
                IntPtr.Zero);
        }
    }
}
  • NativeMethods.cs
// this class just wraps some Win32 stuff that we're going to use
internal class NativeMethods
{
    public const int HWND_BROADCAST = 0xffff;
    public static readonly int WM_SHOWME = RegisterWindowMessage("WM_SHOWME");
    [DllImport("user32")]
    public static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);
    [DllImport("user32")]
    public static extern int RegisterWindowMessage(string message);
}
  • Form1.cs(前面部分)
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }
    protected override void WndProc(ref Message m)
    {
        if(m.Msg == NativeMethods.WM_SHOWME) {
            ShowMe();
        }
        base.WndProc(ref m);
    }
    private void ShowMe()
    {
        if(WindowState == FormWindowState.Minimized) {
            WindowState = FormWindowState.Normal;
        }
        // get our current "TopMost" value (ours will always be false though)
        bool top = TopMost;
        // make our form jump to the top of everything
        TopMost = true;
        // set it back to whatever it was
        TopMost = top;
    }
}

5
この回答で使用するコードとライブラリーが少なくなり、最上位の機能への昇格が提供されることに基づいて、これを新しい受け入れられた回答にします。APIを使用してフォームを一番上に表示するためのより正しい方法を誰かが知っている場合は、遠慮なく追加してください。
Nidonocu、2009

11
@BlueRaja、最初のアプリインスタンスを起動します。2番目のアプリインスタンスを起動すると、別のインスタンスがすでに実行されていることが検出され、シャットダウンの準備が行われます。そうする前に、最初のインスタンスに「SHOWME」ネイティブメッセージを送信し、最初のインスタンスを先頭にします。.NETのイベントはプロセス間通信を許可しないため、ネイティブメッセージが使用されます。
マット・デイヴィス

7
他のインスタンスからコマンドラインを渡す方法はありますか?
gyurisc

22
@Nam、Mutexコンストラクタは文字列を必要とするだけなので、たとえば「This Is My Mutex」など、必要な文字列名を指定できます。「Mutex」は他のプロセスで使用できるシステムオブジェクトであるため、通常は名前を一意にして、同じシステム上の他の「Mutex」名と競合しないようにする必要があります。記事では、不可解な文字列は「ガイド」です。これをプログラムで生成するには、を呼び出しSystem.Guid.NewGuid()ます。この記事の場合、ユーザーはおそらく次のようにVisual Studioを介して生成しました: msdn.microsoft.com/en-us/library/ms241442(VS.80).aspx
Matt Davis

6
mutexアプローチでは、同じユーザーがアプリケーションを再度起動しようとしていると想定していますか?確かに「アプリケーションの既存のインスタンスをフォアグラウンドに」することは、「ユーザーの切り替え」の後に意味をなさない
dumbledad

107

Mutexクラスを使用することもできますが、引数などを渡すためにコードを実装する必要があることがすぐにわかります。さて、Chris Sellの本を読んだとき、WinFormsでプログラミングするときにトリックを学びました。このトリックは、フレームワークですでに利用可能なロジックを使用しています。私はあなたのことは知りませんが、フレームワークで再利用できるものについて学ぶとき、それは通常、車輪を再発明する代わりに私が取るルートです。もちろんそれが私が望むすべてをしているのでない限り。

WPFに入ったとき、同じコードをWPFアプリケーションで使用する方法を思いつきました。このソリューションは、質問に基づいてニーズを満たす必要があります。

まず、アプリケーションクラスを作成する必要があります。このクラスでは、OnStartupイベントをオーバーライドし、後で使用するActivateというメソッドを作成します。

public class SingleInstanceApplication : System.Windows.Application
{
    protected override void OnStartup(System.Windows.StartupEventArgs e)
    {
        // Call the OnStartup event on our base class
        base.OnStartup(e);

        // Create our MainWindow and show it
        MainWindow window = new MainWindow();
        window.Show();
    }

    public void Activate()
    {
        // Reactivate the main window
        MainWindow.Activate();
    }
}

次に、インスタンスを管理できるクラスを作成する必要があります。その前に、Microsoft.VisualBasicアセンブリにあるコードを実際に再利用します。この例ではC#を使用しているので、アセンブリへの参照を作成する必要がありました。VB.NETを使用している場合は、何もする必要はありません。ここで使用するクラスはWindowsFormsApplicationBaseであり、インスタンスマネージャーを継承して、プロパティとイベントを利用して単一のインスタンスを処理します。

public class SingleInstanceManager : Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase
{
    private SingleInstanceApplication _application;
    private System.Collections.ObjectModel.ReadOnlyCollection<string> _commandLine;

    public SingleInstanceManager()
    {
        IsSingleInstance = true;
    }

    protected override bool OnStartup(Microsoft.VisualBasic.ApplicationServices.StartupEventArgs eventArgs)
    {
        // First time _application is launched
        _commandLine = eventArgs.CommandLine;
        _application = new SingleInstanceApplication();
        _application.Run();
        return false;
    }

    protected override void OnStartupNextInstance(StartupNextInstanceEventArgs eventArgs)
    {
        // Subsequent launches
        base.OnStartupNextInstance(eventArgs);
        _commandLine = eventArgs.CommandLine;
        _application.Activate();
    }
}

基本的に、VBビットを使用して単一のインスタンスを検出し、それに応じて処理します。OnStartupは、最初のインスタンスが読み込まれたときに発生します。OnStartupNextInstanceは、アプリケーションが再度実行されるときに発生します。ご覧のとおり、コマンドラインで渡されたものにイベント引数を介してアクセスできます。値をインスタンスフィールドに設定します。ここでコマンドラインを解析するか、コンストラクターとActivateメソッドの呼び出しを介してコマンドラインをアプリケーションに渡すことができます。

3番目に、EntryPointを作成します。通常のようにアプリケーションを更新する代わりに、SingleInstanceManagerを利用します。

public class EntryPoint
{
    [STAThread]
    public static void Main(string[] args)
    {
        SingleInstanceManager manager = new SingleInstanceManager();
        manager.Run(args);
    }
}

まあ、私はあなたがすべてに従って、この実装を使用し、それをあなた自身のものにすることができることを願っています。


9
フォームとは何の関係もないので、mutexソリューションを使用します。
Steven Sudit、2009

1
他のアプローチに問題があったため、これを使用しましたが、内部でリモート処理を使用していると確信しています。私のアプリには2つの関連する問題がありました-一部のお客様は、電話をかけないように言ったにもかかわらず、自宅に電話をかけようとすると言っています。より注意深く見ると、接続はlocalhostになります。それでも、彼らは最初はそれを知りません。また、別の目的でリモート処理を使用することはできません(そうですか?)。既に使用されているためです。mutexアプローチを試したところ、リモート処理を再度使用できました。
Richard Watson

4
私を許してください、しかし私が何かを見落とさない限り、あなたは3行のコードを書くのを避け、代わりにそれを行うためにかなり重いコードを書くためだけにフレームワークを再利用しました。それでは、貯蓄はどこにあるのでしょうか?
greenoldman 2011

2
winformsでそれを行うことは可能ですか?
Jack

1
アプリケーションインスタンスでInitializeComponent()を呼び出さないと、リソースを解決できません... _application = new SingleInstanceApplication(); _application.InitializeComponent(); _application.Run();
Nick

84

ここから。

クロスプロセスMutexの一般的な用途は、プログラムのインスタンスのみが一度に実行できるようにすることです。方法は次のとおりです。

class OneAtATimePlease {

  // Use a name unique to the application (eg include your company URL)
  static Mutex mutex = new Mutex (false, "oreilly.com OneAtATimeDemo");

  static void Main()
  {
    // Wait 5 seconds if contended – in case another instance
    // of the program is in the process of shutting down.
    if (!mutex.WaitOne(TimeSpan.FromSeconds (5), false))
    {
        Console.WriteLine("Another instance of the app is running. Bye!");
        return;
    }

    try
    {    
        Console.WriteLine("Running - press Enter to exit");
        Console.ReadLine();
    }
    finally
    {
        mutex.ReleaseMutex();
    }    
  }    
}

Mutexの優れた機能は、最初にReleaseMutexが呼び出されずにアプリケーションが終了した場合、CLRが自動的にMutexを解放することです。


5
WinFormsに依存していないという事実のために、私はこの答えが受け入れられたものよりもはるかに好きだと言わざるを得ません。個人的に私の開発のほとんどはWPFに移行しており、このようなもののためにWinFormライブラリを引き込む必要はありません。
スウィッター、2008年

5
もちろん、完全な回答を得るには、引数を他のインスタンスに渡すことも説明する必要があります:)
Simon Buchan

@ジェイソン、いいね、ありがとう!しかし、私はタイムアウトを渡したくない。それは非常に主観的であり、非常に多くの変数に依存しています。別のアプリを起動できるようにしたい場合は、ミューテックス
Eric Ouellet

@EricOuellet:タブのあるほぼすべてのプログラムがこれを実行します-Photoshop、Sublime Text、Chrome...。「マスター」プロセスを行う正当な理由がある場合(たとえば、設定にインプロセスDBがある場合)新しいプロセスであるかのようにUIを表示したい。
Simon Buchan

@サイモン、あなたは正しい。私は非常に古いことについて自分自身に質問します... MDI対SDI(マルチドキュメントインターフェイスvsシングルドキュメントインターフェイス)。タブについて話すときは、MDIを指します。1998年に、Microsoftの本はすべてのMDIアプリを排除することを提案しています。MicrosoftはWord、Excel ...をSDIに切り替えましたが、これはシンプルで優れていると思います。Chromeと他のユーザー(現在はIE)がMDIに戻りたいことを理解しています。私は個人的に(何も/個人的な感情に基づいて)、ファイルの関連付けが選択されている場合でも新しいアプリを開く方が良いと考えています。しかし、私は今尋ねられた質問をよりよく理解しています。よろしくお願いします!
Eric Ouellet、

58

MSDNには、実際にこれを行うためのC#とVBの両方のサンプルアプリケーションがあります。http://msdn.microsoft.com/en-us/library/ms771662(v = VS.90).aspx

単一インスタンス検出を開発するための最も一般的で信頼性の高い手法は、Microsoft .NET Frameworkリモート処理インフラストラクチャ(System.Remoting)を使用することです。Microsoft .NET Framework(バージョン2.0)には、必要なリモート機能をカプセル化するWindowsFormsApplicationBaseタイプが含まれています。この型をWPFアプリケーションに組み込むには、その型をWPFアプリケーションから派生させ、アプリケーションの静的エントリポイントメソッドであるMainとWPFアプリケーションのアプリケーション型の間のシムとして使用する必要があります。シムは、アプリケーションが最初に起動されたとき、および後続の起動が試行されたときを検出し、WPFアプリケーションタイプを制御して、起動の処理方法を決定します。

  • C#の場合、人々は深呼吸して、 'VisualBasic DLLを含めたくない'全体を忘れてしまいます。以下のため、この、何スコットHanselman氏が言うと、これはほとんど問題にクリーンなソリューションであり、複数のフレームワークについてのあなたがより多くのことを知っている人々によって設計されているという事実。
  • 使いやすさの観点からは、ユーザーがアプリケーションをロードしていて、それがすでに開いていて、そのようなエラーメッセージが表示されている'Another instance of the app is running. Bye'場合は、ユーザーは非常に満足しているとは言えません。単に(GUIアプリケーションで)そのアプリケーションに切り替えて、提供された引数を渡す必要があります。または、コマンドラインパラメータに意味がない場合は、最小化されている可能性のあるアプリケーションをポップアップする必要があります。

フレームワークはすでにこれをサポートしています-ある馬鹿がDLL Microsoft.VisualBasicと名付けただけで、そのMicrosoft.ApplicationUtilsようなものには入れられませんでした。それを乗り越える-またはリフレクターを開きます。

ヒント:あるとして、あなたはまさにこのアプローチを使用すると、あなたはすでになどのリソースをApp.xamlを持っている場合は、あなたがしたいと思うあまり、このを見てみましょう


「こちらもご覧ください」リンクを含めていただきありがとうございます。それがまさに私が必要としていたことです。ちなみに、リンクのソリューション#3が最適です。
Eternal21

私はまた、可能であればフレームワークと特別に設計されたライブラリーに委任することを提唱しています。
Eniola

23

このコードはmainメソッドに移動する必要があります。見て、ここで WPFの主要な方法の詳細については。

[DllImport("user32.dll")]
private static extern Boolean ShowWindow(IntPtr hWnd, Int32 nCmdShow);

private const int SW_SHOWMAXIMIZED = 3;

static void Main() 
{
    Process currentProcess = Process.GetCurrentProcess();
    var runningProcess = (from process in Process.GetProcesses()
                          where
                            process.Id != currentProcess.Id &&
                            process.ProcessName.Equals(
                              currentProcess.ProcessName,
                              StringComparison.Ordinal)
                          select process).FirstOrDefault();
    if (runningProcess != null)
    {
        ShowWindow(runningProcess.MainWindowHandle, SW_SHOWMAXIMIZED);
       return; 
    }
}

方法2

static void Main()
{
    string procName = Process.GetCurrentProcess().ProcessName;
    // get the list of all processes by that name

    Process[] processes=Process.GetProcessesByName(procName);

    if (processes.Length > 1)
    {
        MessageBox.Show(procName + " already running");  
        return;
    } 
    else
    {
        // Application.Run(...);
    }
}

注:上記のメソッドは、プロセス/アプリケーションに一意の名前があることを前提としています。プロセス名を使用して既存のプロセッサがあるかどうかを確認するためです。したがって、アプリケーションに非常に一般的な名前(メモ帳など)がある場合、上記のアプローチは機能しません。


1
また、コンピューター上で同じ名前のプログラムが他に実行されている場合、これは機能しません。ProcessName実行ファイル名からexe。を除いた値を返します。「メモ帳」と呼ばれるアプリケーションを作成し、Windowsのメモ帳が実行されている場合、アプリケーションが実行されていると検出されます。
Jcl、2015年

1
この回答をありがとう。私は非常に多くの同様の質問を見つけましたが、答えは常に非常に複雑で混乱しているため、役に立たないことがわかりました。これ(方法#1)は単純明快で、何よりも実際にコードを実行するのに役立ちました。
ElDoRado1239 2017年

20

まあ、私はほとんどのユースケースで簡単に機能する使い捨てクラスを持っています:

次のように使用します。

static void Main()
{
    using (SingleInstanceMutex sim = new SingleInstanceMutex())
    {
        if (sim.IsOtherInstanceRunning)
        {
            Application.Exit();
        }

        // Initialize program here.
    }
}

ここにあります:

/// <summary>
/// Represents a <see cref="SingleInstanceMutex"/> class.
/// </summary>
public partial class SingleInstanceMutex : IDisposable
{
    #region Fields

    /// <summary>
    /// Indicator whether another instance of this application is running or not.
    /// </summary>
    private bool isNoOtherInstanceRunning;

    /// <summary>
    /// The <see cref="Mutex"/> used to ask for other instances of this application.
    /// </summary>
    private Mutex singleInstanceMutex = null;

    /// <summary>
    /// An indicator whether this object is beeing actively disposed or not.
    /// </summary>
    private bool disposed;

    #endregion

    #region Constructor

    /// <summary>
    /// Initializes a new instance of the <see cref="SingleInstanceMutex"/> class.
    /// </summary>
    public SingleInstanceMutex()
    {
        this.singleInstanceMutex = new Mutex(true, Assembly.GetCallingAssembly().FullName, out this.isNoOtherInstanceRunning);
    }

    #endregion

    #region Properties

    /// <summary>
    /// Gets an indicator whether another instance of the application is running or not.
    /// </summary>
    public bool IsOtherInstanceRunning
    {
        get
        {
            return !this.isNoOtherInstanceRunning;
        }
    }

    #endregion

    #region Methods

    /// <summary>
    /// Closes the <see cref="SingleInstanceMutex"/>.
    /// </summary>
    public void Close()
    {
        this.ThrowIfDisposed();
        this.singleInstanceMutex.Close();
    }

    public void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            /* Release unmanaged ressources */

            if (disposing)
            {
                /* Release managed ressources */
                this.Close();
            }

            this.disposed = true;
        }
    }

    /// <summary>
    /// Throws an exception if something is tried to be done with an already disposed object.
    /// </summary>
    /// <remarks>
    /// All public methods of the class must first call this.
    /// </remarks>
    public void ThrowIfDisposed()
    {
        if (this.disposed)
        {
            throw new ObjectDisposedException(this.GetType().Name);
        }
    }

    #endregion
}

1
これは簡単に動作するようになりました。Application.Exit()を変更するまで、2番目のアプリケーションは閉じません。単純な戻りに。それ以外は素晴らしいです。前の解決策はインターフェースを使用しているので、詳しく見ていきます。blogs.microsoft.co.il/blogs/arik/archive/2010/05/28/...
HAL9000

15

ミューテックスやIPCなどを使用し、コマンドライン引数を実行中のインスタンスに渡す新しいものは、WPFシングルインスタンスアプリケーションです。


私はこれを大成功で使っています。これにNamedPipesを組み込むと、コマンドライン引数を元のアプリケーションに渡すこともできます。クラス「SingleInstance.cs」は、Microsoftによって作成されました。CodeProjectに関するArik Poznanskiのブログのより読みやすいバージョンへの別のリンクを追加しました。
Heliac 14

1
リンクが壊れています。
Mike Lowery

11

コードC#.NETシングルインスタンスアプリケーションマークされた回答のリファレンスであるは、すばらしいスタートです。

ただし、そのダイアログがマネージダイアログ(aboutボックスなどの別のフォームのような)であるか、アンマネージダイアログ(のように標準の.NETクラスを使用している場合でもOpenFileDialog)。元のコードでは、メインフォームはアクティブ化されていますが、モーダルフォームは非アクティブのままであり、奇妙に見えます。さらに、ユーザーがアプリを使い続けるには、フォームをクリックする必要があります。

それで、WinformsとWPFアプリケーションのためにこれらすべてを自動的に処理するSingleInstanceユーティリティクラスを作成しました。

Winforms

1)Programクラスを次のように変更します。

static class Program
{
    public static readonly SingleInstance Singleton = new SingleInstance(typeof(Program).FullName);

    [STAThread]
    static void Main(string[] args)
    {
        // NOTE: if this always return false, close & restart Visual Studio
        // this is probably due to the vshost.exe thing
        Singleton.RunFirstInstance(() =>
        {
            SingleInstanceMain(args);
        });
    }

    public static void SingleInstanceMain(string[] args)
    {
        // standard code that was in Main now goes here
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }
}

2)メインウィンドウクラスを次のように変更します。

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    protected override void WndProc(ref Message m)
    {
        // if needed, the singleton will restore this window
        Program.Singleton.OnWndProc(this, m, true);

        // TODO: handle specific messages here if needed
        base.WndProc(ref m);
    }
}

WPF:

1)Appページを次のように変更します(また、Mainメソッドを再定義できるように、ビルドアクションをページに設定していることを確認してください)。

public partial class App : Application
{
    public static readonly SingleInstance Singleton = new SingleInstance(typeof(App).FullName);

    [STAThread]
    public static void Main(string[] args)
    {
        // NOTE: if this always return false, close & restart Visual Studio
        // this is probably due to the vshost.exe thing
        Singleton.RunFirstInstance(() =>
        {
            SingleInstanceMain(args);
        });
    }

    public static void SingleInstanceMain(string[] args)
    {
        // standard code that was in Main now goes here
        App app = new App();
        app.InitializeComponent();
        app.Run();
    }
}

2)メインウィンドウクラスを次のように変更します。

public partial class MainWindow : Window
{
    private HwndSource _source;

    public MainWindow()
    {
        InitializeComponent();
    }

    protected override void OnSourceInitialized(EventArgs e)
    {
        base.OnSourceInitialized(e);
        _source = (HwndSource)PresentationSource.FromVisual(this);
        _source.AddHook(HwndSourceHook);
    }

    protected virtual IntPtr HwndSourceHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        // if needed, the singleton will restore this window
        App.Singleton.OnWndProc(hwnd, msg, wParam, lParam, true, true);

        // TODO: handle other specific message
        return IntPtr.Zero;
    }

そしてここにユーティリティクラスがあります:

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Threading;

namespace SingleInstanceUtilities
{
    public sealed class SingleInstance
    {
        private const int HWND_BROADCAST = 0xFFFF;

        [DllImport("user32.dll")]
        private static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);

        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        private static extern int RegisterWindowMessage(string message);

        [DllImport("user32.dll")]
        private static extern bool SetForegroundWindow(IntPtr hWnd);

        public SingleInstance(string uniqueName)
        {
            if (uniqueName == null)
                throw new ArgumentNullException("uniqueName");

            Mutex = new Mutex(true, uniqueName);
            Message = RegisterWindowMessage("WM_" + uniqueName);
        }

        public Mutex Mutex { get; private set; }
        public int Message { get; private set; }

        public void RunFirstInstance(Action action)
        {
            RunFirstInstance(action, IntPtr.Zero, IntPtr.Zero);
        }

        // NOTE: if this always return false, close & restart Visual Studio
        // this is probably due to the vshost.exe thing
        public void RunFirstInstance(Action action, IntPtr wParam, IntPtr lParam)
        {
            if (action == null)
                throw new ArgumentNullException("action");

            if (WaitForMutext(wParam, lParam))
            {
                try
                {
                    action();
                }
                finally
                {
                    ReleaseMutex();
                }
            }
        }

        public static void ActivateWindow(IntPtr hwnd)
        {
            if (hwnd == IntPtr.Zero)
                return;

            FormUtilities.ActivateWindow(FormUtilities.GetModalWindow(hwnd));
        }

        public void OnWndProc(IntPtr hwnd, int m, IntPtr wParam, IntPtr lParam, bool restorePlacement, bool activate)
        {
            if (m == Message)
            {
                if (restorePlacement)
                {
                    WindowPlacement placement = WindowPlacement.GetPlacement(hwnd, false);
                    if (placement.IsValid && placement.IsMinimized)
                    {
                        const int SW_SHOWNORMAL = 1;
                        placement.ShowCmd = SW_SHOWNORMAL;
                        placement.SetPlacement(hwnd);
                    }
                }

                if (activate)
                {
                    SetForegroundWindow(hwnd);
                    FormUtilities.ActivateWindow(FormUtilities.GetModalWindow(hwnd));
                }
            }
        }

#if WINFORMS // define this for Winforms apps
        public void OnWndProc(System.Windows.Forms.Form form, int m, IntPtr wParam, IntPtr lParam, bool activate)
        {
            if (form == null)
                throw new ArgumentNullException("form");

            if (m == Message)
            {
                if (activate)
                {
                    if (form.WindowState == System.Windows.Forms.FormWindowState.Minimized)
                    {
                        form.WindowState = System.Windows.Forms.FormWindowState.Normal;
                    }

                    form.Activate();
                    FormUtilities.ActivateWindow(FormUtilities.GetModalWindow(form.Handle));
                }
            }
        }

        public void OnWndProc(System.Windows.Forms.Form form, System.Windows.Forms.Message m, bool activate)
        {
            if (form == null)
                throw new ArgumentNullException("form");

            OnWndProc(form, m.Msg, m.WParam, m.LParam, activate);
        }
#endif

        public void ReleaseMutex()
        {
            Mutex.ReleaseMutex();
        }

        public bool WaitForMutext(bool force, IntPtr wParam, IntPtr lParam)
        {
            bool b = PrivateWaitForMutext(force);
            if (!b)
            {
                PostMessage((IntPtr)HWND_BROADCAST, Message, wParam, lParam);
            }
            return b;
        }

        public bool WaitForMutext(IntPtr wParam, IntPtr lParam)
        {
            return WaitForMutext(false, wParam, lParam);
        }

        private bool PrivateWaitForMutext(bool force)
        {
            if (force)
                return true;

            try
            {
                return Mutex.WaitOne(TimeSpan.Zero, true);
            }
            catch (AbandonedMutexException)
            {
                return true;
            }
        }
    }

    // NOTE: don't add any field or public get/set property, as this must exactly map to Windows' WINDOWPLACEMENT structure
    [StructLayout(LayoutKind.Sequential)]
    public struct WindowPlacement
    {
        public int Length { get; set; }
        public int Flags { get; set; }
        public int ShowCmd { get; set; }
        public int MinPositionX { get; set; }
        public int MinPositionY { get; set; }
        public int MaxPositionX { get; set; }
        public int MaxPositionY { get; set; }
        public int NormalPositionLeft { get; set; }
        public int NormalPositionTop { get; set; }
        public int NormalPositionRight { get; set; }
        public int NormalPositionBottom { get; set; }

        [DllImport("user32.dll", SetLastError = true)]
        private static extern bool SetWindowPlacement(IntPtr hWnd, ref WindowPlacement lpwndpl);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern bool GetWindowPlacement(IntPtr hWnd, ref WindowPlacement lpwndpl);

        private const int SW_SHOWMINIMIZED = 2;

        public bool IsMinimized
        {
            get
            {
                return ShowCmd == SW_SHOWMINIMIZED;
            }
        }

        public bool IsValid
        {
            get
            {
                return Length == Marshal.SizeOf(typeof(WindowPlacement));
            }
        }

        public void SetPlacement(IntPtr windowHandle)
        {
            SetWindowPlacement(windowHandle, ref this);
        }

        public static WindowPlacement GetPlacement(IntPtr windowHandle, bool throwOnError)
        {
            WindowPlacement placement = new WindowPlacement();
            if (windowHandle == IntPtr.Zero)
                return placement;

            placement.Length = Marshal.SizeOf(typeof(WindowPlacement));
            if (!GetWindowPlacement(windowHandle, ref placement))
            {
                if (throwOnError)
                    throw new Win32Exception(Marshal.GetLastWin32Error());

                return new WindowPlacement();
            }
            return placement;
        }
    }

    public static class FormUtilities
    {
        [DllImport("user32.dll")]
        private static extern IntPtr GetWindow(IntPtr hWnd, int uCmd);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern IntPtr SetActiveWindow(IntPtr hWnd);

        [DllImport("user32.dll")]
        private static extern bool IsWindowVisible(IntPtr hWnd);

        [DllImport("kernel32.dll")]
        public static extern int GetCurrentThreadId();

        private delegate bool EnumChildrenCallback(IntPtr hwnd, IntPtr lParam);

        [DllImport("user32.dll")]
        private static extern bool EnumThreadWindows(int dwThreadId, EnumChildrenCallback lpEnumFunc, IntPtr lParam);

        private class ModalWindowUtil
        {
            private const int GW_OWNER = 4;
            private int _maxOwnershipLevel;
            private IntPtr _maxOwnershipHandle;

            private bool EnumChildren(IntPtr hwnd, IntPtr lParam)
            {
                int level = 1;
                if (IsWindowVisible(hwnd) && IsOwned(lParam, hwnd, ref level))
                {
                    if (level > _maxOwnershipLevel)
                    {
                        _maxOwnershipHandle = hwnd;
                        _maxOwnershipLevel = level;
                    }
                }
                return true;
            }

            private static bool IsOwned(IntPtr owner, IntPtr hwnd, ref int level)
            {
                IntPtr o = GetWindow(hwnd, GW_OWNER);
                if (o == IntPtr.Zero)
                    return false;

                if (o == owner)
                    return true;

                level++;
                return IsOwned(owner, o, ref level);
            }

            public static void ActivateWindow(IntPtr hwnd)
            {
                if (hwnd != IntPtr.Zero)
                {
                    SetActiveWindow(hwnd);
                }
            }

            public static IntPtr GetModalWindow(IntPtr owner)
            {
                ModalWindowUtil util = new ModalWindowUtil();
                EnumThreadWindows(GetCurrentThreadId(), util.EnumChildren, owner);
                return util._maxOwnershipHandle; // may be IntPtr.Zero
            }
        }

        public static void ActivateWindow(IntPtr hwnd)
        {
            ModalWindowUtil.ActivateWindow(hwnd);
        }

        public static IntPtr GetModalWindow(IntPtr owner)
        {
            return ModalWindowUtil.GetModalWindow(owner);
        }
    }
}

10

以下は、アプリケーションの単一インスタンスを可能にする例です。新しいインスタンスが読み込まれると、実行中のメインインスタンスに引数が渡されます。

public partial class App : Application
{
    private static Mutex SingleMutex;
    public static uint MessageId;

    private void Application_Startup(object sender, StartupEventArgs e)
    {
        IntPtr Result;
        IntPtr SendOk;
        Win32.COPYDATASTRUCT CopyData;
        string[] Args;
        IntPtr CopyDataMem;
        bool AllowMultipleInstances = false;

        Args = Environment.GetCommandLineArgs();

        // TODO: Replace {00000000-0000-0000-0000-000000000000} with your application's GUID
        MessageId   = Win32.RegisterWindowMessage("{00000000-0000-0000-0000-000000000000}");
        SingleMutex = new Mutex(false, "AppName");

        if ((AllowMultipleInstances) || (!AllowMultipleInstances && SingleMutex.WaitOne(1, true)))
        {
            new Main();
        }
        else if (Args.Length > 1)
        {
            foreach (Process Proc in Process.GetProcesses())
            {
                SendOk = Win32.SendMessageTimeout(Proc.MainWindowHandle, MessageId, IntPtr.Zero, IntPtr.Zero,
                    Win32.SendMessageTimeoutFlags.SMTO_BLOCK | Win32.SendMessageTimeoutFlags.SMTO_ABORTIFHUNG,
                    2000, out Result);

                if (SendOk == IntPtr.Zero)
                    continue;
                if ((uint)Result != MessageId)
                    continue;

                CopyDataMem = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Win32.COPYDATASTRUCT)));

                CopyData.dwData = IntPtr.Zero;
                CopyData.cbData = Args[1].Length*2;
                CopyData.lpData = Marshal.StringToHGlobalUni(Args[1]);

                Marshal.StructureToPtr(CopyData, CopyDataMem, false);

                Win32.SendMessageTimeout(Proc.MainWindowHandle, Win32.WM_COPYDATA, IntPtr.Zero, CopyDataMem,
                    Win32.SendMessageTimeoutFlags.SMTO_BLOCK | Win32.SendMessageTimeoutFlags.SMTO_ABORTIFHUNG,
                    5000, out Result);

                Marshal.FreeHGlobal(CopyData.lpData);
                Marshal.FreeHGlobal(CopyDataMem);
            }

            Shutdown(0);
        }
    }
}

public partial class Main : Window
{
    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        HwndSource Source;

        Source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
        Source.AddHook(new HwndSourceHook(Window_Proc));
    }

    private IntPtr Window_Proc(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam, ref bool Handled)
    {
        Win32.COPYDATASTRUCT CopyData;
        string Path;

        if (Msg == Win32.WM_COPYDATA)
        {
            CopyData = (Win32.COPYDATASTRUCT)Marshal.PtrToStructure(lParam, typeof(Win32.COPYDATASTRUCT));
            Path = Marshal.PtrToStringUni(CopyData.lpData, CopyData.cbData / 2);

            if (WindowState == WindowState.Minimized)
            {
                // Restore window from tray
            }

            // Do whatever we want with information

            Activate();
            Focus();
        }

        if (Msg == App.MessageId)
        {
            Handled = true;
            return new IntPtr(App.MessageId);
        }

        return IntPtr.Zero;
    }
}

public class Win32
{
    public const uint WM_COPYDATA = 0x004A;

    public struct COPYDATASTRUCT
    {
        public IntPtr dwData;
        public int    cbData;
        public IntPtr lpData;
    }

    [Flags]
    public enum SendMessageTimeoutFlags : uint
    {
        SMTO_NORMAL             = 0x0000,
        SMTO_BLOCK              = 0x0001,
        SMTO_ABORTIFHUNG        = 0x0002,
        SMTO_NOTIMEOUTIFNOTHUNG = 0x0008
    }

    [DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Auto)]
    public static extern uint RegisterWindowMessage(string lpString);
    [DllImport("user32.dll")]
    public static extern IntPtr SendMessageTimeout(
        IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam,
        SendMessageTimeoutFlags fuFlags, uint uTimeout, out IntPtr lpdwResult);
}

これは私が何をすべきかについての本当に良い例です。ネイサン、すべての引数はこのメソッドを使用して送信されますか?私のアプリには7ほどありますが、このコードは機能すると思います
kevp '30年

1
私の例では、最初の引数のみが送信されますが、すべてを送信するように変更できます。
Nathan Moinvaziri

8

ちょっと考えてみてください:一部のユーザーが信じているように、アプリケーションの1つのインスタンスのみが「不完全」でないことが要求される場合があります。データベースアプリなどは、1人のユーザーがアプリの複数のインスタンスにデータベースへのアクセスを許可すると、さらに困難になります(ユーザーのアプリの複数のインスタンスで開いているすべてのレコードを更新することで、マシンなど)。最初に、「名前の衝突については、人間が読み取れる名前を使用しないでください。代わりにGUIDを使用するか、さらにはGUID +人間が読み取れる名前を使用してください。名前の衝突の可能性はレーダーから外れ、Mutexは気にしませんだれかが指摘したように、DOS攻撃はひどいものですが、悪意のある人物がミューテックス名を取得してアプリに組み込むという問題に直面した場合、とにかくあなたはほとんどターゲットであり、ミューテックスの名前をいじるだけでなく、自分を保護するためにもっと多くのことをしなければならないでしょう。また、new Mutex(true、 "some GUID plus Name"、out AIsFirstInstance)のバリアントを使用する場合、Mutexが最初のインスタンスであるかどうかに関するインジケーターはすでにあります。


6

このような一見単​​純な質問への非常に多くの答え。ここで少し物事を揺さぶることは、この問題の私の解決策です。

ミューテックスを作成すると、JIT-erがコードのごく一部にそれを使用しているだけで、ガベージコレクションの準備ができているとマークしたいので、面倒な場合があります。それはあなたがそのMutexをその間長く使用しないだろうと思っていることをあなたをスマートにしたいと思っています。実際には、アプリケーションが実行されている限り、このミューテックスを使い続けたいと思います。ガベージコレクターにMutexをそのままにしておくように伝える最良の方法は、さまざまな世代のガレージコレクションを使用していても、Mutexを存続させることです。例:

var m = new Mutex(...);
...
GC.KeepAlive(m);

このページからアイデアを引き上げました:http : //www.ai.uga.edu/~mc/SingleInstance.html


3
その共有コピーをアプリケーションクラスに格納する方が簡単ではないでしょうか。
rossisdead

6

これを処理する本当に良い方法があるようです:

WPFシングルインスタンスアプリケーション

これは、すべてのミューテックスとメッセージングクラフを管理する追加可能なクラスを提供し、実装を単純化するまで単純化します。


私が試したとき、これは既存のウィンドウを前面に持っていくようには見えませんでした。
RandomEngy 2012年

6

次のコードは、シングルインスタンスアプリケーションを登録するためのWCF名前付きパイプソリューションです。また、別のインスタンスが起動しようとしたときにイベントを発生させ、他のインスタンスのコマンドラインを受け取るので、すばらしいです。

System.Windows.StartupEventHandlerクラスを使用するため、WPFを対象としていますが、これは簡単に変更できます。

このコードは、への参照を必要PresentationFrameworkSystem.ServiceModel

使用法:

class Program
{
    static void Main()
    {
        var applicationId = new Guid("b54f7b0d-87f9-4df9-9686-4d8fd76066dc");

        if (SingleInstanceManager.VerifySingleInstance(applicationId))
        {
            SingleInstanceManager.OtherInstanceStarted += OnOtherInstanceStarted;

            // Start the application
        }
    }

    static void OnOtherInstanceStarted(object sender, StartupEventArgs e)
    {
        // Do something in response to another instance starting up.
    }
}

ソースコード:

/// <summary>
/// A class to use for single-instance applications.
/// </summary>
public static class SingleInstanceManager
{
  /// <summary>
  /// Raised when another instance attempts to start up.
  /// </summary>
  public static event StartupEventHandler OtherInstanceStarted;

  /// <summary>
  /// Checks to see if this instance is the first instance running on this machine.  If it is not, this method will
  /// send the main instance this instance's startup information.
  /// </summary>
  /// <param name="guid">The application's unique identifier.</param>
  /// <returns>True if this instance is the main instance.</returns>
  public static bool VerifySingleInstace(Guid guid)
  {
    if (!AttemptPublishService(guid))
    {
      NotifyMainInstance(guid);

      return false;
    }

    return true;
  }

  /// <summary>
  /// Attempts to publish the service.
  /// </summary>
  /// <param name="guid">The application's unique identifier.</param>
  /// <returns>True if the service was published successfully.</returns>
  private static bool AttemptPublishService(Guid guid)
  {
    try
    {
      ServiceHost serviceHost = new ServiceHost(typeof(SingleInstance));
      NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
      serviceHost.AddServiceEndpoint(typeof(ISingleInstance), binding, CreateAddress(guid));
      serviceHost.Open();

      return true;
    }
    catch
    {
      return false;
    }
  }

  /// <summary>
  /// Notifies the main instance that this instance is attempting to start up.
  /// </summary>
  /// <param name="guid">The application's unique identifier.</param>
  private static void NotifyMainInstance(Guid guid)
  {
    NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
    EndpointAddress remoteAddress = new EndpointAddress(CreateAddress(guid));
    using (ChannelFactory<ISingleInstance> factory = new ChannelFactory<ISingleInstance>(binding, remoteAddress))
    {
      ISingleInstance singleInstance = factory.CreateChannel();
      singleInstance.NotifyMainInstance(Environment.GetCommandLineArgs());
    }
  }

  /// <summary>
  /// Creates an address to publish/contact the service at based on a globally unique identifier.
  /// </summary>
  /// <param name="guid">The identifier for the application.</param>
  /// <returns>The address to publish/contact the service.</returns>
  private static string CreateAddress(Guid guid)
  {
    return string.Format(CultureInfo.CurrentCulture, "net.pipe://localhost/{0}", guid);
  }

  /// <summary>
  /// The interface that describes the single instance service.
  /// </summary>
  [ServiceContract]
  private interface ISingleInstance
  {
    /// <summary>
    /// Notifies the main instance that another instance of the application attempted to start.
    /// </summary>
    /// <param name="args">The other instance's command-line arguments.</param>
    [OperationContract]
    void NotifyMainInstance(string[] args);
  }

  /// <summary>
  /// The implementation of the single instance service interface.
  /// </summary>
  private class SingleInstance : ISingleInstance
  {
    /// <summary>
    /// Notifies the main instance that another instance of the application attempted to start.
    /// </summary>
    /// <param name="args">The other instance's command-line arguments.</param>
    public void NotifyMainInstance(string[] args)
    {
      if (OtherInstanceStarted != null)
      {
        Type type = typeof(StartupEventArgs);
        ConstructorInfo constructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
        StartupEventArgs e = (StartupEventArgs)constructor.Invoke(null);
        FieldInfo argsField = type.GetField("_args", BindingFlags.Instance | BindingFlags.NonPublic);
        Debug.Assert(argsField != null);
        argsField.SetValue(e, args);

        OtherInstanceStarted(null, e);
      }
    }
  }
}

5

単一インスタンスアプリケーションを実装するために名前付きミューテックスを使用しないでください(少なくとも製品コードでは使用しないでください)。悪意のあるコードは、お尻簡単にDoS(サービス拒否)することができます...


8
「名前付きミューテックスは絶対に使用しないでください」-絶対に言わないでください。悪意のあるコードが私のマシン上で実行されている場合、私はおそらくすでに夢中になっています。
Joe

実際、悪意のあるコードである必要はありません。名前の偶然の衝突である可能性があります。
Matt Davison、

次に、あなたは何をすべきですか?
Kevin Berridge、

より良い質問は、あなたがその振る舞いを望んでいる理由として考えられるものです。アプリを単一インスタンスapplication()として設計しないでください。私はそれが不十分な答えであることを知っていますが、設計の観点からは、ほとんど常に正しい答えです。アプリについてもっと知らなければ、もっと言うのは難しいです。
Matt Davison、

2
少なくともWindowsでは、Mutexeはアクセス制御を持っているので、オブジェクトを操作することができます。衝突自体の名前付けについては、UUID / GUIDが発明されたのはそのためです。
NuSkooler

5

次のコードを見てください。これは、WPFアプリケーションの複数のインスタンスを防止するための優れたシンプルなソリューションです。

private void Application_Startup(object sender, StartupEventArgs e)
{
    Process thisProc = Process.GetCurrentProcess();
    if (Process.GetProcessesByName(thisProc.ProcessName).Length > 1)
    {
        MessageBox.Show("Application running");
        Application.Current.Shutdown();
        return;
    }

    var wLogin = new LoginWindow();

    if (wLogin.ShowDialog() == true)
    {
        var wMain = new Main();
        wMain.WindowState = WindowState.Maximized;
        wMain.Show();
    }
    else
    {
        Application.Current.Shutdown();
    }
}

4

これが私が使うものです。プロセスの列挙を組み合わせてスイッチングとミューテックスを実行し、「アクティブなクリッカー」から保護します。

public partial class App
{
    [DllImport("user32")]
    private static extern int OpenIcon(IntPtr hWnd);

    [DllImport("user32.dll")]
    private static extern bool SetForegroundWindow(IntPtr hWnd);

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        var p = Process
           .GetProcessesByName(Process.GetCurrentProcess().ProcessName);
            foreach (var t in p.Where(t => t.MainWindowHandle != IntPtr.Zero))
            {
                OpenIcon(t.MainWindowHandle);
                SetForegroundWindow(t.MainWindowHandle);
                Current.Shutdown();
                return;
            }

            // there is a chance the user tries to click on the icon repeatedly
            // and the process cannot be discovered yet
            bool createdNew;
            var mutex = new Mutex(true, "MyAwesomeApp", 
               out createdNew);  // must be a variable, though it is unused - 
            // we just need a bit of time until the process shows up
            if (!createdNew)
            {
                Current.Shutdown();
                return;
            }

            new Bootstrapper().Run();
        }
    }

4

私は、Dale Raganのものに似たより簡単な解決策を見つけましたが、少し変更されました。実際に必要なすべてのことを行い、標準のMicrosoft WindowsFormsApplicationBaseクラスに基づいています。

まず、Windowsフォームを使用する他のすべての単一インスタンスアプリケーションで使用できるSingleInstanceControllerクラスを作成します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.VisualBasic.ApplicationServices;


namespace SingleInstanceController_NET
{
    public class SingleInstanceController
    : WindowsFormsApplicationBase
    {
        public delegate Form CreateMainForm();
        public delegate void StartNextInstanceDelegate(Form mainWindow);
        CreateMainForm formCreation;
        StartNextInstanceDelegate onStartNextInstance;
        public SingleInstanceController(CreateMainForm formCreation, StartNextInstanceDelegate onStartNextInstance)
        {
            // Set whether the application is single instance
            this.formCreation = formCreation;
            this.onStartNextInstance = onStartNextInstance;
            this.IsSingleInstance = true;

            this.StartupNextInstance += new StartupNextInstanceEventHandler(this_StartupNextInstance);                      
        }

        void this_StartupNextInstance(object sender, StartupNextInstanceEventArgs e)
        {
            if (onStartNextInstance != null)
            {
                onStartNextInstance(this.MainForm); // This code will be executed when the user tries to start the running program again,
                                                    // for example, by clicking on the exe file.
            }                                       // This code can determine how to re-activate the existing main window of the running application.
        }

        protected override void OnCreateMainForm()
        {
            // Instantiate your main application form
            this.MainForm = formCreation();
        }

        public void Run()
        {
            string[] commandLine = new string[0];
            base.Run(commandLine);
        }
    }
}

その後、次のようにプログラムで使用できます。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using SingleInstanceController_NET;

namespace SingleInstance
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        static Form CreateForm()
        {
            return new Form1(); // Form1 is used for the main window.
        }

        static void OnStartNextInstance(Form mainWindow) // When the user tries to restart the application again,
                                                         // the main window is activated again.
        {
            mainWindow.WindowState = FormWindowState.Maximized;
        }
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);            
            SingleInstanceController controller = new SingleInstanceController(CreateForm, OnStartNextInstance);
            controller.Run();         
        }
    }
}

プログラムとSingleInstanceController_NETソリューションの両方がMicrosoft.VisualBasicを参照する必要があります。ユーザーが実行中のプログラムを再起動しようとしたときに、実行中のアプリケーションを通常のウィンドウとして再アクティブ化する場合は、SingleInstanceControllerの2番目のパラメーターをnullにすることができます。与えられた例では、ウィンドウは最大化されています。


4

2017-01-25を更新します。いくつかのことを試した後、私はVisualBasic.dllを使用することにしました。私は以前の答えを参考にさせてください...

参考までに、これは引数を渡さずに行った方法です(理由はわかりません...あるインスタンスから別のインスタンスに渡される引数を持つ単一のアプリを意味します)。ファイルの関連付けが必要な場合は、ドキュメントごとに(ユーザーの標準的な期待に基づいて)アプリをインスタンス化する必要があります。既存のアプリに引数を渡す必要がある場合は、vb dllを使用すると思います。

引数を渡さないで(単一インスタンスアプリのみ)、新しいウィンドウメッセージを登録せず、Matt Davis Solutionで定義されているメッセージループを上書きしません。VisualBasic dllを追加することは大したことではありませんが、単一インスタンスアプリを実行するためだけに新しい参照を追加することは好みません。また、できるだけ早く終了するために、App.StartupオーバーライドからShutdownを呼び出すのではなく、Mainで新しいクラスをインスタンス化することを好みます。

誰かがそれを好きになることを願って...または少し刺激を与えるでしょう:-)

プロジェクトのスタートアップクラスは「SingleInstanceApp」として設定する必要があります。

public class SingleInstanceApp
{
    [STAThread]
    public static void Main(string[] args)
    {
        Mutex _mutexSingleInstance = new Mutex(true, "MonitorMeSingleInstance");

        if (_mutexSingleInstance.WaitOne(TimeSpan.Zero, true))
        {
            try
            {
                var app = new App();
                app.InitializeComponent();
                app.Run();

            }
            finally
            {
                _mutexSingleInstance.ReleaseMutex();
                _mutexSingleInstance.Close();
            }
        }
        else
        {
            MessageBox.Show("One instance is already running.");

            var processes = Process.GetProcessesByName(Assembly.GetEntryAssembly().GetName().Name);
            {
                if (processes.Length > 1)
                {
                    foreach (var process in processes)
                    {
                        if (process.Id != Process.GetCurrentProcess().Id)
                        {
                            WindowHelper.SetForegroundWindow(process.MainWindowHandle);
                        }
                    }
                }
            }
        }
    }
}

WindowHelper:

using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Threading;

namespace HQ.Util.Unmanaged
{
    public class WindowHelper
    {
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool SetForegroundWindow(IntPtr hWnd);

3

しかし、Mutexを使用しない、簡単な答え:

System.Diagnostics;    
...
string thisprocessname = Process.GetCurrentProcess().ProcessName;

if (Process.GetProcesses().Count(p => p.ProcessName == thisprocessname) > 1)
                return;

中に入れてくださいProgram.Main()

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Diagnostics;

namespace Sample
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            //simple add Diagnostics namespace, and these 3 lines below 
            string thisprocessname = Process.GetCurrentProcess().ProcessName;
            if (Process.GetProcesses().Count(p => p.ProcessName == thisprocessname) > 1)
                return;

            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Sample());
        }
    }
}

MessageBox.Show- ifステートメントに追加して、「すでに実行中のアプリケーション」を置くことができます。
これは誰かに役立つかもしれません。


4
2つのプロセスが同時に開始する場合、両方のプロセスが2つのアクティブなプロセスを見て、自己終了する場合があります。
AT

@ATはい、これは管理者として実行しているアプリなどにも役立ちます
newbieguy 2017

アプリケーションのコピーを作成して名前を変更すると、オリジナルとコピーを同時に実行できます。
Dominique Bijnens、

2

名前付きミューテックスはMonoではグローバルではないため、名前付きミューテックスベースのアプローチはクロスプラットフォームではありません。プロセス列挙ベースのアプローチには同期がなく、不正な動作が発生する可能性があります(たとえば、同時に開始された複数のプロセスがすべて、タイミングによってはすべて自己終了する場合があります)。ウィンドウシステムベースのアプローチは、コンソールアプリケーションでは望ましくありません。Divinの回答に基づいて構築されたこのソリューションは、次のすべての問題に対処します。

using System;
using System.IO;

namespace TestCs
{
    public class Program
    {
        // The app id must be unique. Generate a new guid for your application. 
        public static string AppId = "01234567-89ab-cdef-0123-456789abcdef";

        // The stream is stored globally to ensure that it won't be disposed before the application terminates.
        public static FileStream UniqueInstanceStream;

        public static int Main(string[] args)
        {
            EnsureUniqueInstance();

            // Your code here.

            return 0;
        }

        private static void EnsureUniqueInstance()
        {
            // Note: If you want the check to be per-user, use Environment.SpecialFolder.ApplicationData instead.
            string lockDir = Path.Combine(
                Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
                "UniqueInstanceApps");
            string lockPath = Path.Combine(lockDir, $"{AppId}.unique");

            Directory.CreateDirectory(lockDir);

            try
            {
                // Create the file with exclusive write access. If this fails, then another process is executing.
                UniqueInstanceStream = File.Open(lockPath, FileMode.Create, FileAccess.Write, FileShare.None);

                // Although only the line above should be sufficient, when debugging with a vshost on Visual Studio
                // (that acts as a proxy), the IO exception isn't passed to the application before a Write is executed.
                UniqueInstanceStream.Write(new byte[] { 0 }, 0, 1);
                UniqueInstanceStream.Flush();
            }
            catch
            {
                throw new Exception("Another instance of the application is already running.");
            }
        }
    }
}

2

私のソリューションでは、複数のインスタンスを防ぐためにMutexを使用しています。

static Mutex mutex = null;
//A string that is the name of the mutex
string mutexName = @"Global\test";
//Prevent Multiple Instances of Application
bool onlyInstance = false;
mutex = new Mutex(true, mutexName, out onlyInstance);

if (!onlyInstance)
{
  MessageBox.Show("You are already running this application in your system.", "Already Running..", MessageBoxButton.OK);
  Application.Current.Shutdown();
}

1

mutexソリューションを使用します。

using System;
using System.Windows.Forms;
using System.Threading;

namespace OneAndOnlyOne
{
static class Program
{
    static String _mutexID = " // generate guid"
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        Boolean _isNotRunning;
        using (Mutex _mutex = new Mutex(true, _mutexID, out _isNotRunning))
        {
            if (_isNotRunning)
            {
                Application.Run(new Form1());
            }
            else
            {
                MessageBox.Show("An instance is already running.");
                return;
            }
        }
    }
}
}

1

これは私が使用する軽量のソリューションです。これにより、カスタムウィンドウメッセージに頼ったり、プロセス名を盲目的に検索したりせずに、アプリケーションが既存のウィンドウを前面に表示できるようになります。

[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);

static readonly string guid = "<Application Guid>";

static void Main()
{
    Mutex mutex = null;
    if (!CreateMutex(out mutex))
        return;

    // Application startup code.

    Environment.SetEnvironmentVariable(guid, null, EnvironmentVariableTarget.User);
}

static bool CreateMutex(out Mutex mutex)
{
    bool createdNew = false;
    mutex = new Mutex(false, guid, out createdNew);

    if (createdNew)
    {
        Process process = Process.GetCurrentProcess();
        string value = process.Id.ToString();

        Environment.SetEnvironmentVariable(guid, value, EnvironmentVariableTarget.User);
    }
    else
    {
        string value = Environment.GetEnvironmentVariable(guid, EnvironmentVariableTarget.User);
        Process process = null;
        int processId = -1;

        if (int.TryParse(value, out processId))
            process = Process.GetProcessById(processId);

        if (process == null || !SetForegroundWindow(process.MainWindowHandle))
            MessageBox.Show("Unable to start application. An instance of this application is already running.");
    }

    return createdNew;
}

編集:mutexとcreatedNewを静的に保存および初期化することもできますが、使い終わったら明示的にmutexを破棄または解放する必要があります。個人的には、アプリケーションがMainの最後に到達せずに終了した場合でも自動的に破棄されるため、mutexをローカルにしておくことをお勧めします。



1

sendMessageメソッドをNativeMethodsクラスに追加しました。

どうやらpostmessageメソッドは、アプリケーションがタスクバーに表示されていない場合には機能しませんが、sendmessageメソッドを使用すると解決します。

class NativeMethods
{
    public const int HWND_BROADCAST = 0xffff;
    public static readonly int WM_SHOWME = RegisterWindowMessage("WM_SHOWME");
    [DllImport("user32")]
    public static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
    [DllImport("user32")]
    public static extern int RegisterWindowMessage(string message);
}

1

これは、Eventを介して実装された同じものです。

public enum ApplicationSingleInstanceMode
{
    CurrentUserSession,
    AllSessionsOfCurrentUser,
    Pc
}

public class ApplicationSingleInstancePerUser: IDisposable
{
    private readonly EventWaitHandle _event;

    /// <summary>
    /// Shows if the current instance of ghost is the first
    /// </summary>
    public bool FirstInstance { get; private set; }

    /// <summary>
    /// Initializes 
    /// </summary>
    /// <param name="applicationName">The application name</param>
    /// <param name="mode">The single mode</param>
    public ApplicationSingleInstancePerUser(string applicationName, ApplicationSingleInstanceMode mode = ApplicationSingleInstanceMode.CurrentUserSession)
    {
        string name;
        if (mode == ApplicationSingleInstanceMode.CurrentUserSession)
            name = $"Local\\{applicationName}";
        else if (mode == ApplicationSingleInstanceMode.AllSessionsOfCurrentUser)
            name = $"Global\\{applicationName}{Environment.UserDomainName}";
        else
            name = $"Global\\{applicationName}";

        try
        {
            bool created;
            _event = new EventWaitHandle(false, EventResetMode.ManualReset, name, out created);
            FirstInstance = created;
        }
        catch
        {
        }
    }

    public void Dispose()
    {
        _event.Dispose();
    }
}

1

[以下のコンソールおよびwpfアプリケーションのサンプルコードを提供しました。]

createdNew名前付きミューテックスインスタンスを作成した後、変数の値をチェックするだけです(以下の例!)。

ブール値createdNewfalseを返します。

「YourApplicationNameHere」という名前のMutexインスタンスがシステムのどこかにすでに作成されている場合

ブール値createdNewtrueを返します。

これがシステム上の「YourApplicationNameHere」という名前の最初のミューテックスである場合。


コンソールアプリケーション-例:

static Mutex m = null;

static void Main(string[] args)
{
    const string mutexName = "YourApplicationNameHere";
    bool createdNew = false;

    try
    {
        // Initializes a new instance of the Mutex class with a Boolean value that indicates 
        // whether the calling thread should have initial ownership of the mutex, a string that is the name of the mutex, 
        // and a Boolean value that, when the method returns, indicates whether the calling thread was granted initial ownership of the mutex.

        using (m = new Mutex(true, mutexName, out createdNew))
        {
            if (!createdNew)
            {
                Console.WriteLine("instance is alreday running... shutting down !!!");
                Console.Read();
                return; // Exit the application
            }

            // Run your windows forms app here
            Console.WriteLine("Single instance app is running!");
            Console.ReadLine();
        }


    }
    catch (Exception ex)
    {

        Console.WriteLine(ex.Message);
        Console.ReadLine();
    }
}

WPFの例:

public partial class App : Application
{
static Mutex m = null;

protected override void OnStartup(StartupEventArgs e)
{

    const string mutexName = "YourApplicationNameHere";
    bool createdNew = false;

    try
    {
        // Initializes a new instance of the Mutex class with a Boolean value that indicates 
        // whether the calling thread should have initial ownership of the mutex, a string that is the name of the mutex, 
        // and a Boolean value that, when the method returns, indicates whether the calling thread was granted initial ownership of the mutex.

        m = new Mutex(true, mutexName, out createdNew);

        if (!createdNew)
        {
            Current.Shutdown(); // Exit the application
        }

    }
    catch (Exception)
    {
        throw;
    }

    base.OnStartup(e);
}


protected override void OnExit(ExitEventArgs e)
{
    if (m != null)
    {
        m.Dispose();
    }
    base.OnExit(e);
}
}

1

C#Winformsの時間節約ソリューション...

Program.cs:

using System;
using System.Windows.Forms;
// needs reference to Microsoft.VisualBasic
using Microsoft.VisualBasic.ApplicationServices;  

namespace YourNamespace
{
    public class SingleInstanceController : WindowsFormsApplicationBase
    {
        public SingleInstanceController()
        {
            this.IsSingleInstance = true;
        }

        protected override void OnStartupNextInstance(StartupNextInstanceEventArgs e)
        {
            e.BringToForeground = true;
            base.OnStartupNextInstance(e);
        }

        protected override void OnCreateMainForm()
        {
            this.MainForm = new Form1();
        }
    }

    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            string[] args = Environment.GetCommandLineArgs();
            SingleInstanceController controller = new SingleInstanceController();
            controller.Run(args);
        }
    }
}

1

ここから提案された解決策を確認してくださいセマフォを使用して既存のインスタンスが既に実行されているかどうかを判断し、WPFアプリケーションで機能し、TcpListenerとTcpClientを使用して、2番目のインスタンスから最初に実行中のインスタンスに引数を渡すことができる。

.NET Frameworkだけでなく、.NET Coreでも機能します。


1

私はここで短い解決策を見つけることができないので、誰かがこれを好きになることを願っています:

2018年9月20日更新

このコードをあなたの中に入れてくださいProgram.cs

using System.Diagnostics;

static void Main()
{
    Process thisProcess = Process.GetCurrentProcess();
    Process[] allProcesses = Process.GetProcessesByName(thisProcess.ProcessName);
    if (allProcesses.Length > 1)
    {
        // Don't put a MessageBox in here because the user could spam this MessageBox.
        return;
    }

    // Optional code. If you don't want that someone runs your ".exe" with a different name:

    string exeName = AppDomain.CurrentDomain.FriendlyName;
    // in debug mode, don't forget that you don't use your normal .exe name.
    // Debug uses the .vshost.exe.
    if (exeName != "the name of your executable.exe") 
    {
        // You can add a MessageBox here if you want.
        // To point out to users that the name got changed and maybe what the name should be or something like that^^ 
        MessageBox.Show("The executable name should be \"the name of your executable.exe\"", 
            "Wrong executable name", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return;
    }

    // Following code is default code:
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new MainForm());
}

これにより、競合状態が発生します。ミューテックスを使用する必要があります。
georgiosd 2018

1
2つのインスタンスを同時に起動した場合、これが機能するという保証はありません。2つの異なるスレッドから変数を更新するように。トリッキーな危険なビジネス。力を使う、ルーク:)
georgiosd '19 / 09/18

@georgiosdああ、私はあなたの意味を理解しています。誰かが.exeを起動して名前を変更するようなものです。はい、これはそれをより多く起動する方法ですが、通常、名前が変更された場合、.exeは機能しません。私は私の答えを更新します^^ルーク:Dを指摘してくれてありがとう:)
Deniz

1
@Denizだけではありません。2つのプロセスを非常に高速に開始すると、プロセスリストまたはそれらをフェッチするメソッドが、まだ1つしか表示されていない間に実行される可能性があります。これはあなたには無関係なエッジケースかもしれませんが、これは一般的な質問です...
georgiosd

@georgiosd証明できますか?Iv'eがheheのためだけにテストしたからです。しかし、本当に「本当に速い」としても、私には不可能でした。:Pだから私はなぜあなたが真実ではない何かに生きているのか理解できず、この無邪気なコードさえ嫌いです:D
Deniz
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.