Nobody's Laboratory


// Ego Dominvs Mevs Svm



I had to implement this feature to my little WPF project, and this is the documentation of the things I learned while doing the research.

Goal

  • Make the process kill itself if it’s duplicated
  • Show the window of the original process instead

Steps

  1. Create a named mutex.
  2. Check if it failed to create a named mutex (That would mean there’s already a mutex with the same name).
  3. If so, call Win32 API functions to bring the window of the original process to the foreground and exit.

Code

Win32.cs

using System.Runtime.InteropServices;

namespace WPFApp;

static class Win32
{
    public const int SW_RESTORE = 9;

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

    [DllImport("user32")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool IsIconic(IntPtr hWnd);
    
    [DllImport("user32")]
    public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
}

App.xaml.cs

using System.IO.MemoryMappedFiles;
using System.IO;
using System.Windows;
using System.Windows.Interop;

namespace WPFApp;

/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
    private static Mutex? mutex = null;

    private const string mutexName = "Unique Mutex Name for Your App";
    private const string mmfName = "Unique MMF Name for Your App";

    public static void FocusWindow(IntPtr hWnd)
    {
        if (Win32.IsIconic(hWnd))
        {
            Win32.ShowWindow(hWnd, Win32.SW_RESTORE);
        }
        Win32.SetForegroundWindow(hWnd);
    }

    public static IntPtr RetrieveWindowHandle()
    {
        using var mmf = MemoryMappedFile.OpenExisting(mmfName);
        using var stream = mmf.CreateViewStream();

        var reader = new BinaryReader(stream);
        return new IntPtr(reader.ReadInt64());
    }

    public static void ShareWindowHandleThread(IntPtr hWnd)
    {
        new Thread(() =>
        {
            using var mmf = MemoryMappedFile.CreateNew(mmfName, 1024);

            using (var stream = mmf.CreateViewStream())
            {
                var writer = new BinaryWriter(stream);
                writer.Write(hWnd.ToInt64());
            }

            Thread.Sleep(Timeout.Infinite);
        })
        {
            IsBackground = true
        }
        .Start();
    }

    protected override void OnStartup(StartupEventArgs e)
    {
        mutex = new(true, mutexName, out bool createdNew);

        if (!createdNew)
        {
            IntPtr hWnd = RetrieveWindowHandle();
            if (hWnd != IntPtr.Zero)
            {
                FocusWindow(hWnd);
            }
            Environment.Exit(0);
        }

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

        nint handle = new WindowInteropHelper(MainWindow).Handle;

        ShareWindowHandleThread(handle);

        base.OnStartup(e);
    }

    protected override void OnExit(ExitEventArgs e)
    {
        if (mutex != null)
        {
            mutex.ReleaseMutex();
            mutex = null;
        }

        base.OnExit(e);
    }
}

Note

  1. Creating the named mutex is to determine whether there’s a duplicate program/process running. I could check the process name or the process path but I thought this would be more simple and straightforward.
  2. Creating an MMF, writing the integer value of the main window handle into it and keeping it open while the process is open(ShareWindowHandleThread()), is to get the window handle value faster(RetrieveWindowHandle()), so that it can bring the window of the original process to the foreground(FocusWindow()).
  3. At first I wrote the code to only call Win32.SetForegroundWindow() to bring the window to the foreground, but as it turns out, it doesn’t work when the window is minimized. So, I had to make FocusWindow() function to handle this issue. Win32.IsIconic() function is to determine if the window is minimized(iconic), and Win32.ShowWindow() function is to show the minimized window. And then it calls Win32.SetForegroundWindow() to focus on the window.