Introduction
It is a very common situation when for some reason only one instance of an app could be active on a PC. Unfortunately C# (just like many other languages) does not have such a function, so you have to implement it by yourself. I found a few methods on the Internet about how to do this in C#. Some of them are here - on CodeProject. But I would like to introduce one more, which is probably the fastest and easiest, but has some limitations described below.
Background
This method is based on three Windows API functions: checking a unique custom value in Global atom table - should be the very first function of an app; closing Application if such value is in or adding in case the value was not found; finally - it should be removed from the Atoms Table just before this application finished. Global Atom table is a system table that stores strings and corresponding identifiers which are available to all applications. Applications can create two types of atoms: string atoms and integer atoms. This functionality is supported by any Microsoft Windows API since Windows 95 and implemented in KERNEL32.DLL library.
Using the Code
First of all, we have to import three API functions using Platform invoke and the best place to put it is in the main app file - Program.cs:
using System.Runtime.InteropServices;
...
static class Program
{
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern ushort GlobalAddAtom(string lpString);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern ushort GlobalFindAtom(string lpString);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern ushort GlobalDeleteAtom(ushort atom);
Here is a short description of these functions:
GlobalAddAtom
: adds a character string
to the global atom table and returns a unique value (an atom) identifying the string
. lpString
should be a string
value with maximum size of 255 characters. This parameter could have any value such as a GUID, App path or combination of some Application data such as ProgramName
and ProgramVersion
. String
s differ only in case that are considered identical. If the function succeeds, the return value is the newly created atom. Read full description on MSDN. GlobalFindAtom
: searches the global atom table for the specified character string
and retrieves the global atom associated with that string
. The parameter should be exactly the same that we are going to use in GlobalAddAtom
. If the function succeeds, the return value is the global atom associated with the given string. Read full description on MSDN.GlobalDeleteAtom
: removes the Atom from Global Atoms table and decrements the reference count of a global string atom. The function always returns zero. Read full description on MSDN.
Now we can add these functions in the Main()
method which normally locates in Program.cs file:
[STAThread]
static void Main()
{
string atomStr = Application.ProductName + Application.ProductVersion;
ushort Atom = GlobalFindAtom(atomStr);
if (Atom > 0)
{
MessageBox.Show("You are trying to open second instance of the App!
This instance will be closed.");
}
else
{
Atom = GlobalAddAtom(atomStr);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new mainForm());
GlobalDeleteAtom(Atom);
}
}
Good Things and Bad Things
It was easy, wasn't it? But like any solution, this one has good and bad things.
Let's count the good things first:
- It is easy: just add few lines of code in one place and forget about it!
- Fast at run-time: Some other methods I saw
enum
all opened windows or processes to make an attempt to find first an instance of the App which makes the start process slow; - Flexible: Since you can define an Atom ID by yourself you can use different IDs, for example, App path to disallow second instance only in case user launches the App from the same location, or
ApplicationVersion
to allow to run older and newer versions at the same time and disallow to run the same version twice. - Give your users choice: You can modify this code to request them to choose: Close the App or allow to raise second instance.
Bad things:
- Although this method is pretty safe even in case the App will be closed for some reason by system itself or by user using Ctrl-Alt-Del key combination because function
GlobalDeleteArom()
will do its job anyway, but in case of an unhandled exception the App will be closed immediately and the Atom will stay in Global table which means that user will have to restart his computer to remove the Atom from Atoms table; - In case one instance is already opened, this code won't bring the first instance up to user's face but it could be easily fixed using other API functions such as
FindWindow()
and ShowWindow()
.
Links
History
- 19th February, 2006 - Initial revision
More than 15 years in the industry.
Delphi, C# (Win/WebForms), MS SQL