Implementing "Remove Duplicate Process" Feature in WPF
wpf
18 Dec 2023 - Nobody
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
- Create a named mutex.
- Check if it failed to create a named mutex (That would mean there’s already a mutex with the same name).
- 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
- 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.
- 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()
). - 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 makeFocusWindow()
function to handle this issue.Win32.IsIconic()
function is to determine if the window is minimized(iconic), andWin32.ShowWindow()
function is to show the minimized window. And then it callsWin32.SetForegroundWindow()
to focus on the window.