using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Windows.Automation;
using System.Windows.Forms;
namespace YourSingleInstanceApplication
{
/// <summary>
/// Contains functions used to launch a single-instance-only Windows Forms application.
/// References to UIAutomationClient and UIAutomationTypes are needed for the RestoreExistingToFront() method.
/// </summary>
static class InstanceGuard
{
private static Mutex _mutex;
/// <summary>
/// Runs the application if there are no other instances currently running.
/// If another instance is found, the user is notified and it is brought to the front of the screen.
/// </summary>
/// <param name="allowOtherUsers">Determines whether other users on the same PC can have their own instance.</param>
/// <param name="timeout">Determines how long to wait for a mutex in milliseconds.</param>
/// <typeparam name="TForm">The type of Form class to create as the main application form.</typeparam>
public static void RunOnlyInstance<TForm>(bool allowOtherUsers = false, int timeout = 200) where TForm : Form, new()
{
string product = Application.ProductName;
try
{
if (!CreateInstance(allowOtherUsers, timeout))
{
MessageBox.Show(
$"There is already another instance of {product} running.",
product,
MessageBoxButtons.OK,
MessageBoxIcon.Warning
);
RestoreExistingToFront(true);
Environment.Exit(-1);
}
}
catch (Exception ex)
{
MessageBox.Show(
$"An error occured while trying to launch {product}:\n\n{ex.Message}",
product,
MessageBoxButtons.OK,
MessageBoxIcon.Error
);
Environment.Exit(-1);
}
LaunchInstance<TForm>();
}
/// <summary>
/// Attempts to aquire a mutex used to prevent other instances from being executed.
/// </summary>
/// <param name="allowOtherUsers">Determines whether other users on the same PC can have their own instance.</param>
/// <param name="timeout">Determines how long to wait for a mutex in milliseconds.</param>
/// <returns>Returns true if there are no other instances, otherwise returns false.</returns>
public static bool CreateInstance(bool allowOtherUsers = false, int timeout = 200)
{
string guid = (
((GuidAttribute)Assembly
.GetExecutingAssembly()
.GetCustomAttributes(typeof(GuidAttribute), false)
.GetValue(0))
.Value
);
var mutexName = string.Format(CultureInfo.InvariantCulture, "{0}{{{1}}}", (allowOtherUsers ? "" : "Global\\"), guid);
var securitySettings = new MutexSecurity();
securitySettings.AddAccessRule(
new MutexAccessRule(
new SecurityIdentifier(WellKnownSidType.WorldSid, null),
MutexRights.FullControl,
AccessControlType.Allow
)
);
_mutex = new Mutex(false, mutexName, out bool unused, securitySettings);
try
{
return _mutex.WaitOne(timeout, false);
}
catch (AbandonedMutexException)
{
return true;
}
}
/// <summary>
/// Runs the applications main form and disposes of the mutex on exit/exception.
/// </summary>
public static void LaunchInstance<TForm>() where TForm : Form, new()
{
try
{
Application.Run(new TForm());
}
finally
{
_mutex?.Close();
_mutex?.Dispose();
}
}
/// <summary>
/// Attempts to locate the window of an existing instance and bring it to the front before exiting the current instance.
/// </summary>
public static bool RestoreExistingToFront(bool maximized = true)
{
int currentId = Process.GetCurrentProcess().Id;
string product, currentProduct = Application.ProductName;
var guiProcesses = Process.GetProcesses().Where(p => p.MainWindowHandle != IntPtr.Zero).ToArray();
foreach (var process in guiProcesses)
{
if (process.Id == currentId)
continue;
if (Environment.OSVersion.Version.Major >= 6)
{
string assemblyPath = GetExecutablePathNatively(process.Id);
if (string.IsNullOrWhiteSpace(assemblyPath) || !File.Exists(assemblyPath))
continue;
product = FileVersionInfo.GetVersionInfo(assemblyPath).ProductName;
}
else
{
product = process.MainModule.FileVersionInfo.ProductName;
}
if (product == currentProduct)
{
AutomationElement element = AutomationElement.FromHandle(process.MainWindowHandle);
WindowPattern WPattern = element.GetCurrentPattern(WindowPattern.Pattern) as WindowPattern;
WPattern.SetWindowVisualState(maximized ? WindowVisualState.Maximized : WindowVisualState.Normal);
return true;
}
}
return false;
}
/// <summary>
/// Used to get the executable path of a process on Windows Vista or above without getting 'Access is denied' error.
/// </summary>
internal static string GetExecutablePathNatively(int processId)
{
var buffer = new StringBuilder(1024);
IntPtr handle = OpenProcess(ProcessAccessFlags.ProcessQueryLimitedInformation, false, processId);
if (handle != IntPtr.Zero)
{
try
{
int size = buffer.Capacity;
if (QueryFullProcessImageName(handle, 0, buffer, out size))
return buffer.ToString();
}
finally
{
CloseHandle(handle);
}
}
int errorCode = Marshal.GetLastWin32Error();
if (errorCode != 0)
throw new Win32Exception(errorCode);
else
return null;
}
internal enum ProcessAccessFlags : uint
{
All = 0x001F0FFF,
Terminate = 0x00000001,
CreateThread = 0x00000002,
VMOperation = 0x00000008,
VMRead = 0x00000010,
VMWrite = 0x00000020,
DupHandle = 0x00000040,
SetInformation = 0x00000200,
QueryInformation = 0x00000400,
Synchronize = 0x00100000,
ProcessQueryInformation = 0x0400,
ProcessQueryLimitedInformation = 0x1000,
}
[DllImport("kernel32.dll")]
internal static extern bool QueryFullProcessImageName(IntPtr hProcess, int dwFlags, StringBuilder lpExeName, out int size);
[DllImport("kernel32.dll")]
internal static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool CloseHandle(IntPtr hHandle);
}
}
Static class with methods used to greatly simplify restricting an application to a single instance.
Replace "Application.Run(new TForm())" with "InstanceGuard.RunOnlyInstance<MainForm>()"
to launch your main form or display instance-related errors and restore the existing instance
to the front of the screen.
Replace "Application.Run(new TForm())" with "InstanceGuard.RunOnlyInstance<MainForm>()"
to launch your main form or display instance-related errors and restore the existing instance
to the front of the screen.
Be the first to comment
You can use [html][/html], [css][/css], [php][/php] and more to embed the code. Urls are automatically hyperlinked. Line breaks and paragraphs are automatically generated.