Introduction
Enforcing a rule that only one instance of process is running is an interesting task.
There are many ways to code the algorithm on Win32. I solved the problem by creating a reusable
C# class that encapsulates one of the quirks of the Win32 API and makes the identification of
another "same" process more certain.
This article was triggered (and borrows heavily from) another article
written by Marc Clifton, "Detect if another process is running and bring it to the foreground" which
is also available on CodeProject. When implementing Marc's code I ran into a few issues:
- The code was not in an easily reusable format.
- Identifying processes by name alone can be error prone when the name is common.
- The multi-threaded nature of Win32 could conceivably have an undesirable effect on
the reporting of an initial "same" process instance.
- Long assembly names trigger partially hidden process names in Win32.
Solution
In order to enhance the detection of other instances of a process, I decided to use a
named mutex synchronization object. By definition, Win32 dictates that only one instance of
a named mutex will exist on a system and any given time. Win32 further guarantees that only
one thread will own the mutex at any given time. Since a mutex can cross the process boundary,
it became a good choice for identifying existing processes.
I encapsulated the mutex in a class (SingleProgramInstance
) and had the
constructors attempt to create and gain ownership of the mutex immediately. Failure to
gain ownership indicates the existence of another "same" process currently running. If the
object successfully acquires ownership the program will hold onto it during its operation and
therefore, announce to the system that it is the initial one and only process.
A property (IsSingleInstance
) is used to indicate this state.
There are two constructors for the object. One of the constructors accepts no
parameters and uses the current assembly name to name the mutex. Because assembly names
can be common between different applications, I created a second constructor that accepts a string parameter.
The object appends this string to the assembly name to help differentiate it from other applications.
The programmer can pass in a few meaningless characters to greatly reduce the likelihood of name duplication.
UI Concerns
When another "same" process is identified, it is usually nice to present the
previous instance to the user before terminating the redundant process. I separated
this code from the detection code because it may not be desirable in all circumstances (i.e. tray icon applications).
Essentially, I ask Windows for the all of the processes that match the current process name and use
some Win32 Interop calls to restore and bring the process to the foreground. Herein lies a small quirk with Win32.
Only the first 15 characters of a process name are made available when you ask for a process name.
Unfortunately, when you ask for all of the processes of a certain name (via Process.GetProcessesByName
),
Windows uses the whole name (which may exceed 15 characters) when performing the test.
That leaves us the choice of looping through all the processes ourselves and testing only the
first 15 characters or find out our full process name and ask windows to return only those that match.
Since the first 15 characters may not be unique enough for a good test, I choose the second approach.
Luckily, it seems that the assembly name and the full (hidden) process name are one and the same.
Using the assembly name, Win32 returns only the processes that match the full process name and I can
easily ignore my own process by testing the process id.
Cleanup
Since this object is reliant on a system resource (mutex) a good method for
clean up should be employed. I chose to use the IDisposable
interface
to guarantee a deterministic release of the mutex object. When used properly, the
mutex will be released immediately upon program termination so that an additional process
can again be started. This should happen anyway due to the nature of a mutex but that is
no excuse for sloppy code. Below is a example of the proper use of this object. Notice
that the using
statement neatly wraps the Application.Run()
.
using SpecialServices;
....
[STAThread]
static void Main()
{
using(SingleProgramInstance spi = new SingleProgramInstance("x5k6yz"))
{
if (spi.IsSingleInstance)
{
Application.Run(new Form1());
}
else
{
spi.RaiseOtherProcess();
}
}
}
....
Object Source
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Reflection;
namespace SpecialServices
{
public class SingleProgramInstance : IDisposable
{
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool ShowWindowAsync(IntPtr hWnd,int nCmdShow);
[DllImport("user32.dll")]
private static extern bool IsIconic(IntPtr hWnd);
private const int SW_RESTORE = 9;
private Mutex _processSync;
private bool _owned = false;
public SingleProgramInstance()
{
_processSync = new Mutex(
true,
Assembly.GetExecutingAssembly().GetName().Name,
out _owned);
}
public SingleProgramInstance(string identifier)
{
_processSync = new Mutex(
true,
Assembly.GetExecutingAssembly().GetName().Name + identifier,
out _owned);
}
~SingleProgramInstance()
{
Release();
}
public bool IsSingleInstance
{
get {return _owned;}
}
public void RaiseOtherProcess()
{
Process proc = Process.GetCurrentProcess();
string assemblyName =
Assembly.GetExecutingAssembly().GetName().Name;
foreach (Process otherProc in
Process.GetProcessesByName(assemblyName))
{
if (proc.Id != otherProc.Id)
{
IntPtr hWnd = otherProc.MainWindowHandle;
if (IsIconic(hWnd))
{
ShowWindowAsync(hWnd,SW_RESTORE);
}
SetForegroundWindow(hWnd);
break;
}
}
}
private void Release()
{
if (_owned)
{
_processSync.ReleaseMutex();
_owned = false;
}
}
#region Implementation of IDisposable
public void Dispose()
{
Release();
GC.SuppressFinalize(this);
}
#endregion
}
}
Summary
I would like to thank Marc Clifton for his insightful article that drove me to create the
above solution. Hopefully, others will expand upon what we have done to produce an even more solid solution.