46

これは、単一インスタンスの WPF アプリケーションを作成するためにこれまでに実装したコードです。

#region Using Directives
using System;
using System.Globalization;
using System.Reflection;
using System.Threading;
using System.Windows;
using System.Windows.Interop;
#endregion

namespace MyWPF
{
    public partial class MainApplication : Application, IDisposable
    {
        #region Members
        private Int32 m_Message;
        private Mutex m_Mutex;
        #endregion

        #region Methods: Functions
        private IntPtr HandleMessages(IntPtr handle, Int32 message, IntPtr wParameter, IntPtr lParameter, ref Boolean handled)
        {
            if (message == m_Message)
            {
                if (MainWindow.WindowState == WindowState.Minimized)
                    MainWindow.WindowState = WindowState.Normal;

                Boolean topmost = MainWindow.Topmost;

                MainWindow.Topmost = true;
                MainWindow.Topmost = topmost;
            }

            return IntPtr.Zero;
        }

        private void Dispose(Boolean disposing)
        {
            if (disposing && (m_Mutex != null))
            {
                m_Mutex.ReleaseMutex();
                m_Mutex.Close();
                m_Mutex = null;
            }
        }

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

        #region Methods: Overrides
        protected override void OnStartup(StartupEventArgs e)
        {
            Assembly assembly = Assembly.GetExecutingAssembly();
            Boolean mutexCreated;
            String mutexName = String.Format(CultureInfo.InvariantCulture, "Local\\{{{0}}}{{{1}}}", assembly.GetType().GUID, assembly.GetName().Name);

            m_Mutex = new Mutex(true, mutexName, out mutexCreated);
            m_Message = NativeMethods.RegisterWindowMessage(mutexName);

            if (!mutexCreated)
            {
                m_Mutex = null;

                NativeMethods.PostMessage(NativeMethods.HWND_BROADCAST, m_Message, IntPtr.Zero, IntPtr.Zero);

                Current.Shutdown();

                return;
            }

            base.OnStartup(e);

            MainWindow window = new MainWindow();
            MainWindow = window;
            window.Show(); 

            HwndSource.FromHwnd((new WindowInteropHelper(window)).Handle).AddHook(new HwndSourceHook(HandleMessages));
        }

        protected override void OnExit(ExitEventArgs e)
        {
            Dispose();
            base.OnExit(e);
        }
        #endregion
    }
}

すべてが完璧に機能します...しかし、私はそれについていくつかの疑問を持っています。私のアプローチをどのように改善できるかについて、あなたの提案を受け取りたいと思います.

1)メンバー ( )IDisposableを使用していたため、コード分析からインターフェイスを実装するように求められました。私の実装は十分ですか?呼び出されることはないので、避けるべきですか?IDisposableMutexDispose()

2)m_Mutex = new Mutex(true, mutexName, out mutexCreated);結果を使用して確認するか、使用してから確認するm_Mutex = new Mutex(false, mutexName);方が良いm_Mutex.WaitOne(TimeSpan.Zero, false);ですか?マルチスレッドの場合、つまり...

3) RegisterWindowMessageAPI 呼び出しは返されるはずUInt32ですが、メッセージ値としてHwndSourceHookのみ受け入れInt32られます... 予期しない動作 (結果が よりも大きいなど) について心配する必要がありますInt32.MaxValueか?

4)OnStartupオーバーライドでは...base.OnStartup(e);別のインスタンスが既に実行されていて、アプリケーションをシャットダウンする場合でも実行する必要がありますか?

5) 値を設定する必要のない既存のインスタンスを最上位に移動するより良い方法はありTopmostますか? たぶんActivate()

6) 私のアプローチに欠陥があると思いますか? マルチスレッド、不適切な例外処理などに関するものはありますか? たとえば... と の間OnStartupでアプリケーションがクラッシュした場合はどうなりOnExitますか?

4

12 に答える 12

59

いくつかの選択肢があり、

  • ミューテックス
  • プロセスマネージャー
  • 名前付きセマフォ
  • リスナーソケットを使用する

ミューテックス

Mutex myMutex ;

private void Application_Startup(object sender, StartupEventArgs e)
{
    bool aIsNewInstance = false;
    myMutex = new Mutex(true, "MyWPFApplication", out aIsNewInstance);  
    if (!aIsNewInstance)
    {
        MessageBox.Show("Already an instance is running...");
        App.Current.Shutdown();  
    }
}

プロセスマネージャー

private void Application_Startup(object sender, StartupEventArgs e)
{
    Process proc = Process.GetCurrentProcess();
    int count = Process.GetProcesses().Where(p=> 
        p.ProcessName == proc.ProcessName).Count();

    if (count > 1)
    {
        MessageBox.Show("Already an instance is running...");
        App.Current.Shutdown(); 
    }
}

リスナーソケットを使用する

別のアプリケーションに通知する 1 つの方法は、そのアプリケーションへの Tcp 接続を開くことです。ソケットを作成し、ポートにバインドし、バックグラウンド スレッドで接続をリッスンします。これが成功した場合は、通常どおり実行します。そうでない場合は、そのポートへの接続を作成します。これにより、2 回目のアプリケーション起動試行が行われたことが他のインスタンスに通知されます。その後、元のインスタンスは、必要に応じてメイン ウィンドウを前面に表示できます。

「セキュリティ」ソフトウェア/ファイアウォールが問題になる可能性があります。

シングル インスタンス アプリケーション C#.Net と Win32

于 2013-02-05T07:48:54.797 に答える
49

ユーザー エクスペリエンスをもう少し向上させたいと考えていました。別のインスタンスが既に実行されている場合は、2 番目のインスタンスに関するエラーを表示するのではなく、それをアクティブにしましょう。これが私の実装です。

名前付き Mutex を使用して 1 つのインスタンスのみが実行されていることを確認し、EventWaitHandle という名前を付けて、あるインスタンスから別のインスタンスに通知を渡します。

アプリ.xaml.cs:

/// <summary>Interaction logic for App.xaml</summary>
public partial class App
{
    #region Constants and Fields

    /// <summary>The event mutex name.</summary>
    private const string UniqueEventName = "{GUID}";

    /// <summary>The unique mutex name.</summary>
    private const string UniqueMutexName = "{GUID}";

    /// <summary>The event wait handle.</summary>
    private EventWaitHandle eventWaitHandle;

    /// <summary>The mutex.</summary>
    private Mutex mutex;

    #endregion

    #region Methods

    /// <summary>The app on startup.</summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The e.</param>
    private void AppOnStartup(object sender, StartupEventArgs e)
    {
        bool isOwned;
        this.mutex = new Mutex(true, UniqueMutexName, out isOwned);
        this.eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, UniqueEventName);

        // So, R# would not give a warning that this variable is not used.
        GC.KeepAlive(this.mutex);

        if (isOwned)
        {
            // Spawn a thread which will be waiting for our event
            var thread = new Thread(
                () =>
                {
                    while (this.eventWaitHandle.WaitOne())
                    {
                        Current.Dispatcher.BeginInvoke(
                            (Action)(() => ((MainWindow)Current.MainWindow).BringToForeground()));
                    }
                });

            // It is important mark it as background otherwise it will prevent app from exiting.
            thread.IsBackground = true;

            thread.Start();
            return;
        }

        // Notify other instance so it could bring itself to foreground.
        this.eventWaitHandle.Set();

        // Terminate this instance.
        this.Shutdown();
    }

    #endregion
}

MainWindow.cs の BringToForeground:

    /// <summary>Brings main window to foreground.</summary>
    public void BringToForeground()
    {
        if (this.WindowState == WindowState.Minimized || this.Visibility == Visibility.Hidden)
        {
            this.Show();
            this.WindowState = WindowState.Normal;
        }

        // According to some sources these steps gurantee that an app will be brought to foreground.
        this.Activate();
        this.Topmost = true;
        this.Topmost = false;
        this.Focus();
    }

Startup="AppOnStartup" を追加します (vhanla に感謝します!):

<Application x:Class="MyClass.App"  
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"   
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             Startup="AppOnStartup">
    <Application.Resources>
    </Application.Resources>
</Application>

私のために働きます:)

于 2014-05-19T05:17:39.187 に答える
8

2 番目のインスタンスを防ぐ (そして既存のインスタンスを通知する) には、

  • EventWaitHandle を使用して (イベントについて話しているため)、
  • タスクを使用して、
  • Mutex コードは必要ありません。
  • TCPなし、
  • ピンヴォークなし、
  • GarbageCollection のものはありません。
  • スレッド保存
  • 単純

次のように実行できます (これは WPF アプリの場合 (App() の参照を参照)、WinForms でも機能します):

public partial class App : Application
{
    public App()
    {
        // initiate it. Call it first.
        preventSecond();
    }

    private const string UniqueEventName = "{GENERATE-YOUR-OWN-GUID}";

    private void preventSecond()
    {
        try
        {
            EventWaitHandle.OpenExisting(UniqueEventName); // check if it exists
            this.Shutdown();
        }
        catch (WaitHandleCannotBeOpenedException)
        {
            new EventWaitHandle(false, EventResetMode.AutoReset, UniqueEventName); // register
        }
    }
}

2 番目のバージョン: 上記に加えて、他のインスタンスにウィンドウを表示するように通知します (WinForms の MainWindow 部分を変更します):

public partial class App : Application
{
    public App()
    {
        // initiate it. Call it first.
        //preventSecond();
        SingleInstanceWatcher();
    }

    private const string UniqueEventName = "{GENERATE-YOUR-OWN-GUID}";
    private EventWaitHandle eventWaitHandle;

    /// <summary>prevent a second instance and signal it to bring its mainwindow to foreground</summary>
    /// <seealso cref="https://stackoverflow.com/a/23730146/1644202"/>
    private void SingleInstanceWatcher()
    {
        // check if it is already open.
        try
        {
            // try to open it - if another instance is running, it will exist , if not it will throw
            this.eventWaitHandle = EventWaitHandle.OpenExisting(UniqueEventName);

            // Notify other instance so it could bring itself to foreground.
            this.eventWaitHandle.Set();

            // Terminate this instance.
            this.Shutdown();
        }
        catch (WaitHandleCannotBeOpenedException)
        {
            // listen to a new event (this app instance will be the new "master")
            this.eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, UniqueEventName);
        }

        // if this instance gets the signal to show the main window
        new Task(() =>
        {
            while (this.eventWaitHandle.WaitOne())
            {
                Current.Dispatcher.BeginInvoke((Action)(() =>
                {
                    // could be set or removed anytime
                    if (!Current.MainWindow.Equals(null))
                    {
                        var mw = Current.MainWindow;

                        if (mw.WindowState == WindowState.Minimized || mw.Visibility != Visibility.Visible)
                        {
                            mw.Show();
                            mw.WindowState = WindowState.Normal;
                        }

                        // According to some sources these steps are required to be sure it went to foreground.
                        mw.Activate();
                        mw.Topmost = true;
                        mw.Topmost = false;
                        mw.Focus();
                    }
                }));
            }
        })
        .Start();
    }
}

クラスのドロップとしてのこのコードは、@ Selfcontained-C-Sharp-WPF-compatible-utility-classes / Utils.SingleInstance.csになります。

于 2018-10-09T11:57:35.147 に答える
8

1) 私には標準の Dispose 実装のように見えます。実際には必要ありませんが (ポイント 6 を参照)、害はありません。(閉鎖時の片付けは、家を燃やす前に家を掃除するのと少し似ていますが、私見ですが、この問題に関する意見は異なります..)

とにかく、直接呼び出されなくても、クリーンアップ メソッドの名前として「Dispose」を使用しないのはなぜですか? これを「クリーンアップ」と呼ぶこともできますが、人間向けのコードも書いていることを思い出してください。というわけで「処分」へ。

2) 私は常にm_Mutex = new Mutex(false, mutexName);、技術的な利点というより慣例だと考えています。

3) MSDN から:

メッセージが正常に登録された場合、戻り値は 0xC000 から 0xFFFF の範囲のメッセージ ID です。

だから私は心配しません。通常、このクラスの関数では、「Int に収まらない。もっと何かがあるので UInt を使用しよう」という目的で UInt を使用するのではなく、「関数は決して負の値を返さない」という契約を明確にするために使用されます。

4)#1と同じ理由で、シャットダウンする場合は呼び出すことを避けます

5) いくつかの方法があります。Win32 で最も簡単な方法は、2 番目のインスタンスで SetForegroundWindow を呼び出すことです (ここを参照してください: http://blogs.msdn.com/b/oldnewthing/archive/2009/02/20/9435239.aspx )。ただし、同等の WPF 機能があるかどうか、またはそれを PInvoke する必要があるかどうかはわかりません。

6)

たとえば... OnStartup と OnExit の間にアプリケーションがクラッシュした場合はどうなりますか?

問題ありません。プロセスが終了すると、そのプロセスが所有していたすべてのハンドルが解放されます。ミューテックスも解放されます。

要するに、私の推奨事項:

  • 名前付き同期オブジェクトに基づくアプローチを使用します。これは、Windows プラットフォームでより確立されています。(ターミナル サーバーのようなマルチユーザー システムを検討する場合は注意してください! 同期オブジェクトの名前は、おそらくユーザー名/SID とアプリケーション名の組み合わせになります)
  • Windows API を使用して、前のインスタンス (ポイント #5 のリンクを参照) または WPF の同等物を生成します。
  • おそらくクラッシュについて心配する必要はありません (カーネルはカーネル オブジェクトの参照カウンターを減らします。とにかく少しテストを行ってください)。(Firefoxで起こります..あなたにも起こったと確信しています!ウィンドウなし、ffプロセス、新しいウィンドウを開くことはできません)。その場合、1 つまたは 2 つの手法を組み合わせて、a) アプリケーション/ウィンドウが応答するかどうかをテストすることをお勧めします。b) ハングしたインスタンスを見つけて終了する

たとえば、古いプロセスを見つけて終了するために、テクニック (ウィンドウにメッセージを送信/投稿しようとする - 応答しない場合はスタックしている) と MSK テクニックを使用できます。その後、通常起動。

于 2013-02-05T08:12:55.860 に答える
5

これを処理する最も簡単な方法は、名前付きセマフォを使用することです。このようなものを試してみてください...

public partial class App : Application
{
    Semaphore sema;
    bool shouldRelease = false;

    protected override void OnStartup(StartupEventArgs e)
    {

        bool result = Semaphore.TryOpenExisting("SingleInstanceWPFApp", out sema);

        if (result) // we have another instance running
        {
            App.Current.Shutdown();
        }
        else
        {
            try
            {
                sema = new Semaphore(1, 1, "SingleInstanceWPFApp");
            }
            catch
            {
                App.Current.Shutdown(); //
            }
        }

        if (!sema.WaitOne(0))
        {
            App.Current.Shutdown();
        }
        else
        {
            shouldRelease = true;
        }


        base.OnStartup(e);
    }

    protected override void OnExit(ExitEventArgs e)
    {
        if (sema != null && shouldRelease)
        {
            sema.Release();
        }
    }

}
于 2013-02-02T19:24:36.713 に答える
4

これには単純なTCPソケットを使用しました(10年前のJavaで)。

  1. 起動時に事前定義されたポートに接続します。接続が受け入れられた場合、別のインスタンスが実行されています。そうでない場合は、TCP リスナーを開始します。
  2. 誰かがあなたに接続したら、ウィンドウをポップアップして切断します
于 2013-01-30T11:24:51.773 に答える
2

これは簡単な解決策です。この場合は、MainWindow.xaml のスタートアップ ファイル (アプリケーションの開始場所から表示) を開きます。MainWindow.xaml.cs ファイルを開きます。コンストラクターに移動し、initializecomponent() の後に次のコードを追加します。

Process Currentproc = Process.GetCurrentProcess();

Process[] procByName=Process.GetProcessesByName("notepad");  //Write the name of your exe file in inverted commas
if(procByName.Length>1)
{
  MessageBox.Show("Application is already running");
  App.Current.Shutdown();
 }

System.Diagnostics を追加することを忘れないでください

于 2014-11-28T09:10:13.627 に答える
0

ここで帽子をリングに投げ込むだけです。私がしていることは、すべての WPF アプリケーションで使用する共通ライブラリに保持するApplicationBase通常のクラスのサブクラスを作成することです。Application次に、基本クラスを (XAML とそのコード ビハインド内から) 変更して、自分の基本クラスを使用します。最後にEntryPoint.Main、アプリのスタートアップ オブジェクトとして を使用し、単一インスタンスのステータスをチェックして、最初でない場合は単純に返します。

注: また、別のインスタンスを起動する場合にオーバーライドできるフラグをサポートする方法も示します。ただし、このようなオプションには注意してください。実際に意味がある場合にのみ使用してください。

コードは次のとおりです。

ApplicationBase (アプリケーション サブクラス)

public abstract class ApplicationBase : Application {

    public static string? SingleInstanceId { get; private set; }

    public static bool InitializeAsFirstInstance(string singleInstanceId){

        if(SingleInstanceId != null)
            throw new AlreadyInitializedException(singleInstanceId);

        SingleInstanceId = singleInstanceId;

        var waitHandleName = $"SingleInstanceWaitHandle:{singleInstanceId}";

        if(EventWaitHandle.TryOpenExisting(waitHandleName, out var waitHandle)){

            // An existing WaitHandle was successfuly opened which means we aren't the first so signal the other
            waitHandle.Set();

            // Then indicate we aren't the first instance by returning false
            return false;
        }

        // Welp, there was no existing WaitHandle with this name, so we're the first!
        // Now we have to set up the EventWaitHandle in a task to listen for other attempts to launch

        void taskBody(){

            var singleInstanceWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, waitHandleName);

            while (singleInstanceWaitHandle.WaitOne()) {

                if(Current is ApplicationBase applicationBase)
                    Current.Dispatcher.BeginInvoke(applicationBase.OtherInstanceLaunched);
            }
        }

        new Task(taskBody, TaskCreationOptions.LongRunning).Start();

        return true;
    }

    public static bool IsSingleInstance
        => SingleInstanceId != null;

    protected virtual void OtherInstanceLaunched()
        => Current.MainWindow?.BringToFront();
}

仮想としてマークOtherInstanceLaunchedすることで、単純にオーバーライドしてアプリケーションごとにカスタマイズしたり、デフォルトの実装に任せたりすることができます。ここでは、Window追加した拡張メソッドです。(基本的には、表示されていることを確認し、復元してからフォーカスします。)

EntryPoint.Main

public static class EntryPoint {

    public static class CommandLineArgs{
        public const string AllowMulti = "/AllowMulti";
        public const string NoSplash   = "/NoSplash";
    }

    [STAThread]
    public static int Main(string[] args) {

        var showSplashScreen = true;
        var allowMulti       = false;

        foreach (var arg in args) {

            if (arg.Equals(CommandLineArgs.AllowMulti, StringComparison.CurrentCultureIgnoreCase))
                allowMulti = true;

            if (arg.Equals(CommandLineArgs.NoSplash, StringComparison.CurrentCultureIgnoreCase))
                showSplashScreen = false;
        }

        // Try and initialize myself as the first instance. If I'm not and 'allowMulti' is false, exit with a return code of 1
        if (!ApplicationBase.InitializeAsFirstInstance(ApplicationInfo.ProductName) && !allowMulti)
            return 1;

        if (showSplashScreen) {
            var splashScreen = new SplashScreen("resources/images/splashscreen.png");
            splashScreen.Show(true, false);
        }

        _ = new App();

        return 0;
    }
}

このアプローチの利点は、スプラッシュ スクリーンが表示される前だけでなく、アプリケーション自体がインスタンス化される前でも実行を引き継ぐことです。つまり、可能な限り早い段階で救済します。

注: マルチサポートさえ必要ない場合は、その引数のチェックとテストを削除できます。これは説明のために追加しただけです

于 2020-12-04T05:05:15.283 に答える