51

This is the code I implemented so far to create a single instance WPF application:

#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
    }
}

Everything works perfectly... but I have some doubts about it and I would like to receive your suggestions about how my approach could be improved.

1) I was asked by Code Analysis to implement IDisposable interface because I was using IDisposable members (the Mutex). Is my Dispose() implementation good enough? Should I avoid it because it's never going to be called?

2) It's better to use m_Mutex = new Mutex(true, mutexName, out mutexCreated); and check for the result or to use m_Mutex = new Mutex(false, mutexName); and then check for m_Mutex.WaitOne(TimeSpan.Zero, false); ? In case of multithreading I mean...

3) RegisterWindowMessage API call should return UInt32... but HwndSourceHook is only accepting Int32 as message value... should I be worried about unexpected behaviors (like a result bigger than Int32.MaxValue)?

4) In OnStartup override... should I execute base.OnStartup(e); even if another instance is already running and I'm going to shutdown the application?

5) Is there a better way to bring the existing instance to the top that doesn't need to set Topmost value? Maybe Activate()?

6) Can you see any flaw in my approach? Something concerning multithreading, bad exceptions handling and something like that? For example... what happens if my application crashes between OnStartup and OnExit?

Tommaso Belluzzo
  • 23,232
  • 8
  • 74
  • 98

13 Answers13

66

There are Several choices,

  • Mutex
  • Process manager
  • Named Semaphore
  • Use a listener socket

Mutex

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();  
    }
}

Process manager

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(); 
    }
}

Use a listener socket

One way to signal another application is to open a Tcp connection to it. Create a socket, bind to a port, and listen on a background thread for connections. If this succeeds, run normally. If not, make a connection to that port, which signals the other instance that a second application launch attempt has been made. The original instance can then bring its main window to the front, if appropriate.

“Security” software / firewalls might be an issue.

Single Instance Application C#.Net along with Win32

Hille
  • 2,123
  • 22
  • 39
C-va
  • 2,910
  • 4
  • 27
  • 42
  • 1
    Computers with no NIC/IPAddress -> not a problem, you are on the same machine/will use the loopback adapter – Lorenzo Dematté Feb 05 '13 at 13:19
  • 1
    As an added touch, you may want to add calls to SetForegroundWindow(IntPtr hWnd) and ShowWindowAsync(IntPtr hWnd, int nCmdShow) to bring the currently running instance to the foreground instead of showing a message box. You'll need to use [DllImport("user32.dll")] for this. – Shannon Cook Apr 16 '13 at 22:34
  • Is "MyWPFApplication" name of EXE? – NoWar Dec 17 '13 at 11:46
  • 2
    @Clark - Name of the Mutex. Could be any unique name. So, the mutex allows only one instance under that unique name. – C-va Dec 18 '13 at 07:46
55

I wanted to have a bit better user experience - if another instance is already running let's activate it rather than showing an error about the second instance. Here is my implementation.

I use named Mutex for making sure that only one instance is running and named EventWaitHandle to pass notification from one instance to another.

App.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
}

And BringToForeground in MainWindow.cs:

    /// <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();
    }

And add Startup="AppOnStartup" (thanks 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>

Works for me :)

ZakiMa
  • 5,637
  • 1
  • 24
  • 48
  • 4
    You forgot to mention that we need to add AppOnStartup to the App.Xaml file: `Startup="AppOnStartup"` – vhanla Sep 30 '15 at 20:58
  • 2
    This is a fantastic answer. I was able to plug it into my app almost word for word, and then easily extend it to support my requirements. Works like a charm! – thehelix Dec 01 '17 at 02:01
  • The only downfall is that this use a thread. Probably not good for performance – Altiano Gerung Jun 26 '18 at 02:59
  • 1
    This has solved my problem too so many thanks. I should add that my App.xaml file has Startup="AppOnStartup" and StartupUri="MainWindow.xaml". I also have the minor issue that a network deployed/published application checks for a new version each time the user starts it from the desktop but I can live with that. – miriyo Mar 22 '19 at 19:06
  • 1
    @AltianoGerung, no problem for performance as the thread will not be active when it is waiting on the event. – Adrian S May 05 '22 at 15:23
  • 1
    I like the thread approach - it's better than the old approach of searching for top-level windows with the same name as the app. Just make sure your event name and mutex name really are unique - I use the app's name followed by a GUID created with the Create GUID too. – Adrian S May 05 '22 at 15:25
47

For WPF just use:

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

    protected override void OnStartup(StartupEventArgs e)
    {
        const string appName = "MyAppName";
        bool createdNew;

        _mutex = new Mutex(true, appName, out createdNew);

        if (!createdNew)
        {
            //app is already running! Exiting the application  
            Application.Current.Shutdown();
        }

        base.OnStartup(e);
    }          
}
smedasn
  • 1,249
  • 1
  • 13
  • 16
  • that is the best answer in code shortly , working without use bloody external dll and a large deprecated code. – luka Sep 26 '17 at 08:52
  • Can simply this more and remove an arbitrary string: _mutex = new Mutex(true, this.GetType().Namespace.ToString(), out createdNew); – Geordie Apr 18 '22 at 20:28
12

to prevent a second instance (and signal the existing),

  • using EventWaitHandle (since we are talking about an event),
  • using Task,
  • no Mutex code required,
  • no TCP,
  • no Pinvokes,
  • no GarbageCollection stuff,
  • thread save
  • simple

it could be done like this (this for an WPF app (see ref to App()), but works on WinForms as well):

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
        }
    }
}

Second version: above plus signaling the other instance to show the window (change the MainWindow part for WinForms):

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();
    }
}

This code as a drop in class, will be @ Selfcontained-C-Sharp-WPF-compatible-utility-classes / Utils.SingleInstance.cs

BananaAcid
  • 3,221
  • 35
  • 38
  • 1
    Great solution. I like the way you use the EventWaitHandle instead of relying on a mutex or something similar. Only changes I made was to specifically catch `WaitHandleCannotBeOpenedException`, and I moved the creation of the wait handle to inside the task itself as it doesn't ever need to be global (it can be local in the 'try' block, and a second local inside the task. But again, best solution here. – Mark A. Donohoe Jan 14 '19 at 04:08
  • @MarqueIV Thanks - and only catching the specific exception makes sense. The reason I thought it would be good to have the handle synchronously created outside of the task, was to have the app quit before it spins off the async running task - being a tad more resource friendly and maybe keep any app stuff from flashing up or running (or any message box later in the code, debug etc = blocking code flow). – BananaAcid Jan 14 '19 at 14:18
  • Makes sense. But even if you create it outside the task, it can still be local to that function as the task should capture it, Shouldn’t it? – Mark A. Donohoe Jan 14 '19 at 14:25
  • :D oh sure - I just realised what you meant. It does not have to be in the global scope in that use case. Just left it there, to have accessible, in case there is another method to trigger anything on that eventWaitHandle object or is going to react to it ... – BananaAcid Jan 14 '19 at 14:38
  • 4
    I would suggest two improvements. Firstly that you use TryOpenExisting rather than using an Exception as part of your normal flow and secondly that you register the Task as a long running task - `TaskCreationOptions.LongRunning` - so you aren't using one of the Task pool spaces for the entire lifetime of your application. – Itzalive May 13 '19 at 02:57
  • Well, EventWaitHandle.OpenExisting, _does_ create a synchronization object underneath so I don't understand why it should be better than a named mutex, but it is interesting to see another solution! – Lorenzo Dematté May 21 '20 at 17:02
8

1) It looks like a standard Dispose implementation to me. It is not really necessary (see point 6) but it does not do any harm. (Cleanup on closing it's a bit like cleaning the house before burning it down, IMHO, but opinions on the matter differs..)

Anyway, why not using "Dispose" as the name of the cleanup method, even if it does not get called directly? You could have called it "Cleanup", but remember you also write code for humans, and Dispose looks familiar and anyone on .NET understands what is it for. So, go for "Dispose".

2) I have always seen m_Mutex = new Mutex(false, mutexName); I think it's more a convention that a technical advantage, however.

3) From MSDN:

If the message is successfully registered, the return value is a message identifier in the range 0xC000 through 0xFFFF.

So I would not worry. Usually, for this class of functions, UInt is not used for "it does not fit in Int, let's use UInt so we have something more" but to clarify a contract "function never returns a negative value".

4) I would avoid calling it if you will shutdown, same reason as #1

5) There are a couple of ways of doing it. The easiest way in Win32 is simply to have the second instance make the call to SetForegroundWindow (Look here: http://blogs.msdn.com/b/oldnewthing/archive/2009/02/20/9435239.aspx); however, I don't know if there is an equivalent WPF functionality or if you need to PInvoke it.

6)

For example... what happens if my application crashes between OnStartup and OnExit?

It's OK: when a process terminates, all handles owned by the process are released; the mutex is released as well.

In short, my recommendations:

  • I would used an approach based on named synchronization objects: it is the more established on the windows platform(s). (Be careful when considering a multi-user system, like terminal server! Name the synchronization object as a combination of, maybe, user name/SID and application name)
  • Use the Windows API to raise the previous instance (see my link at point #5), or the WPF equivalent.
  • You probably do not have to worry about crashes (kernel will decrease the ref counter for the kernel object for you; do a little test anyway), BUT If I may suggest an improvement: what if your first application instance does not crash but hangs? (Happens with Firefox.. I'm sure it happened to you too! No window, ff process, you cannot open a new one). In that case it may be good to combine another technique or two, to a) test if the application/window responds; b) find the hung instance and terminate it

For example, you can use your technique (trying to send/post a message to the window - if does not answer back it is stuck), plus MSK technique, to find and terminate the old process. Then start normally.

Lorenzo Dematté
  • 7,638
  • 3
  • 37
  • 77
5

The most straight forward way to handle that would be using a named semaphore. Try something like this...

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();
        }
    }

}
blaise
  • 307
  • 1
  • 5
  • 9
    Umm... what's the difference from the OP code? He uses a named mutex, why a semaphore should be better? – Lorenzo Dematté Feb 05 '13 at 08:01
  • @LorenzoDematté I realise this is an old question of yours, but for others, read [this](https://stackoverflow.com/a/40238/55487) answer for a good analysis of the important differences. – Geoff Mar 06 '20 at 13:33
  • 2
    @Geoff while the answer is an interesting reading, it is for a different OS. In the NT kernel mutexes and semaphores are both kernel dispatcher objects and they act in a pretty similar way. For sure in such a way that for the OP purpose using a semaphore with a count of 1 instead of a mutex has no effect whatsover. – Lorenzo Dematté May 21 '20 at 16:55
5

My Solution for a .Net Core 3 Wpf Single Instance Application:

[STAThread]
public static void Main()
{
    StartSingleInstanceApplication<CntApplication>();
}

public static void StartSingleInstanceApplication<T>()
    where T : RichApplication
{
    DebuggerOutput.GetInstance();

    Assembly assembly = typeof(T).Assembly;
    string mutexName = $"SingleInstanceApplication/{assembly.GetName().Name}/{assembly.GetType().GUID}";

    Mutex mutex = new Mutex(true, mutexName, out bool mutexCreated);

    if (!mutexCreated)
    {
        mutex = null;

        var client = new NamedPipeClientStream(mutexName);
        client.Connect();

        using (StreamWriter writer = new StreamWriter(client))
            writer.Write(string.Join("\t", Environment.GetCommandLineArgs()));

        return;
    }
    else
    {
        T application = Activator.CreateInstance<T>();

        application.Exit += (object sender, ExitEventArgs e) =>
        {
            mutex.ReleaseMutex();
            mutex.Close();
            mutex = null;
        };

        Task.Factory.StartNew(() =>
        {
            while (mutex != null)
            {
                using (var server = new NamedPipeServerStream(mutexName))
                {
                    server.WaitForConnection();

                    using (StreamReader reader = new StreamReader(server))
                    {
                        string[] args = reader.ReadToEnd().Split("\t", StringSplitOptions.RemoveEmptyEntries).ToArray();
                        UIDispatcher.GetInstance().Invoke(() => application.ExecuteCommandLineArgs(args));
                    }
                }
            }
        }, TaskCreationOptions.LongRunning);

        typeof(T).GetMethod("InitializeComponent").Invoke(application, new object[] { });
        application.Run();
    }
}
ffetech
  • 51
  • 1
  • 3
4

I've used a simple TCP socket for this (in Java, 10 years ago).

  1. On startup connect to a predefined port, if the connection is accepted, another instance is running, if not, start a TCP Listener
  2. Once someone connects to you, popup the window and disconnect
Luuk
  • 1,959
  • 1
  • 21
  • 43
  • 1
    if you do so, use a lock file instead of a port – invalidusername Feb 05 '13 at 05:36
  • 8
    @invalidusername I personally dislike lock files: unlike other resources (handles, sockets) they are not "deleted" automatically by the OS if the process crash. Some programs have this (Early versions of Firefox?) and they were incredibly annoying.. – Lorenzo Dematté Feb 05 '13 at 08:24
  • 2
    true, but opening an extra port also seems a strange way to solve this problem. – invalidusername Feb 05 '13 at 17:53
  • 1
    It may be strange, but it works, and it works on almost any system. The only thing I'm concerned about is, I've sometimes seen listening ports stay "open" for a short while even after the application has crashed. – toster-cx May 25 '16 at 07:40
2

This is a simple solution, Open your startup file (View from where your application starts) in this case its MainWindow.xaml. Open your MainWindow.xaml.cs file. Go to the constructor and after intializecomponent() add this code:

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();
 }

Don't forget to add System.Diagnostics

Hamed
  • 21
  • 1
1

Here is example that brings the old instance to foreground aswell:

public partial class App : Application
{
    [DllImport("user32", CharSet = CharSet.Unicode)]
    static extern IntPtr FindWindow(string cls, string win);
    [DllImport("user32")]
    static extern IntPtr SetForegroundWindow(IntPtr hWnd);
    [DllImport("user32")]
    static extern bool IsIconic(IntPtr hWnd);
    [DllImport("user32")]
    static extern bool OpenIcon(IntPtr hWnd);

    private static Mutex _mutex = null;

    protected override void OnStartup(StartupEventArgs e)
    {
        const string appName = "LinkManager";
        bool createdNew;

        _mutex = new Mutex(true, appName, out createdNew);

        if (!createdNew)
        {
            ActivateOtherWindow();
            //app is already running! Exiting the application  
            Application.Current.Shutdown();
        }

        base.OnStartup(e);
    }

    private static void ActivateOtherWindow()
    {
        var other = FindWindow(null, "!YOUR MAIN WINDOW TITLE HERE!");
        if (other != IntPtr.Zero)
        {
            SetForegroundWindow(other);
            if (IsIconic(other))
                OpenIcon(other);
        }
    }
}

But it will only work if your main window title do not change durig runtime.

Edit:

You can also use Startup event in App.xaml instead of overriding OnStartup.

// App.xaml.cs
private void Application_Startup(object sender, StartupEventArgs e)
{
    const string appName = "LinkManager";
    bool createdNew;

    _mutex = new Mutex(true, appName, out createdNew);

    if (!createdNew)
    {
        ActivateOtherWindow();
        //app is already running! Exiting the application  
        Application.Current.Shutdown();
    }
}

// App.xaml
<Application x:Class="MyApp.App"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:local="clr-namespace:MyApp"
         StartupUri="MainWindow.xaml" Startup="Application_Startup"> //<- startup event

Remember to not call base.OnStartup(e) in this case!

Dave_cz
  • 1,180
  • 10
  • 17
  • Solution for .NET 2 and win XP ... But my problem with it is going after titles. I see a lot of potential in getting this broken, hitting security restrictions, not working on future windows environments ... – BananaAcid Oct 21 '18 at 12:38
0

Just throwing my hat into the ring here. What I do is I create an ApplicationBase subclass of the regular Application class which I keep in a common library I use in all my WPF applications. Then I change the base class (from within the XAML and its code-behind) to use my base class. Finally, I use an EntryPoint.Main as the startup object for my app, which I then check the single instance status, and simply return if I'm not the first.

Note: I also show how to support a flag that lets you override that if you want to launch another instance. However, be careful with such an option. Only use it where it makes actual sense.

Here's the code:

ApplicationBase (Application Subclass)

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();
}

By marking OtherInstanceLaunched as virtual, I can customize that on a per-application basis by simply overriding it, or just let the default implementation do its thing, which here, is an extension method on Window that I added. (Essentially it makes sure it's visible, restored, then focuses it.)

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;
    }
}

The advantage of this approach is it hands over execution even before the application itself is instantiated as well as before the splash screen is shown. In other words, it bails out at the earliest possible place.

Note: If you don't even want multi-support, then you can remove that argument check and test. This was just added for illustrative purposes

Mark A. Donohoe
  • 28,442
  • 25
  • 137
  • 286
0

Although the GetProcessesByName().Length method works, Mutex is the computer-wide, C# lock needed. Since WPF does not auto-generate GUIDs in assemblyInfo as with WinForms, the app itself must self-generate a unique identifier. Also note that the Mutex must remain visible for entire application life-cycle; otherwise, it is automatically released on dispose. Call the Mutex.WaitOne() method to lock and Mutex.ReleaseMutex() to unlock. Reference: Mutex, Threading in C# by Joe Albahari ... www.albahari.com/threading/

private Mutex mutex = new Mutex(false, <Author> + <AppName>);
private void Application_Startup(object sender, StartupEventArgs e)
{
    if (!mutex.WaitOne()) { App.Current.Shutdown(<ExitCode>); }
    else                  { new MainWindow(e.Args); }
}
-1

Best one go with process name,

Interaction logic for App.xaml

`

    [DllImport("user32.dll")]
    public static extern bool ShowWindowAsync(HandleRef hWnd, int nCmdShow);
    [DllImport("user32.dll")]
    public static extern bool SetForegroundWindow(IntPtr WindowHandle);
    public const int SW_RESTORE = 9;
    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)
        {                
            Process process = Process.GetProcessesByName(proc.ProcessName).FirstOrDefault();
            IntPtr hWnd = IntPtr.Zero;
            hWnd = process.MainWindowHandle;
            ShowWindowAsync(new HandleRef(null, hWnd), SW_RESTORE);
            SetForegroundWindow(process.MainWindowHandle);
            App.Current.Shutdown();
        }
    }

`