Introduction
A quick Google search will list a variety of ways to limit an application to running only one instance per computer, but what about the case where there are multiple users who are logged in simultaneously? How do you allow each user to only run one instance?
Note: The condition described by this article is when you have multiple users on the same computer, and not the case where you have a single user logged into multiple computers that are networked together.
Limiting the Application to a Single Instance
Limiting the application to only running a single instance can be done simply by getting the current process and comparing the process Id for each process with the same name to that of the current process. If another process is found, show it instead. This is a fairly well documented method of limiting the application to only one instance.
Example code is shown below:
[STAThread]
static void Main()
{
Process runningInstance = GetRunningInstance();
if (runningInstance == null)
{
Form mainForm = new Form1();
UserInstance user = new UserInstance(mainForm.Handle);
Application.Run(mainForm);
user.Dispose();
}
else
{
ShowWindow (runningInstance.MainWindowHandle, 1);
}
}
public static Process GetRunningInstance()
{
Process current = Process.GetCurrentProcess();
Process[] processes =
Process.GetProcessesByName(current.ProcessName);
foreach (Process process in processes)
{
if ((process.Id != current.Id) &&
UserInstance.IsSameUser(process.MainWindowHandle))
{
return process;
}
}
return null;
}
[DllImport ("user32.dll", CharSet = CharSet.Auto)]
public static extern bool ShowWindow (IntPtr hWnd, int cmdShow);
Starting an Instance for a Different User
Okay, simple enough, but what if user "Mom" is running the application and then user "Dad" switches to his desktop and wants to start the same application? The application will run briefly, detect the already existing instance and exit.
My first attempt was to try to handle this by comparing the value of System.Environment.Username
to process.StartInfo.EnvironmentVariables["username"]
. If they are equal, then the instance is running for the same user. This did not work because Windows XP resets the process to belong to the active user.
My work-around was to set a window property on the main window that includes the username. This requires importing a couple more methods from the User32
DLL and disposing of the object so you can release the allocated memory. To handle this, I created a simple class that sets the property upon construct and has a static
method to determine if this is the same user who opened the original instance.
public class UserInstance: IDisposable
{
private static string myPropertyName =
"USERNAME." + System.Environment.UserName;
private IntPtr handle = IntPtr.Zero;
private bool disposed = false;
public UserInstance(IntPtr hWin)
{
this.handle = hWin;
SetProp(handle, myPropertyName, (int)hWin);
}
~UserInstance()
{
Dispose();
}
public void Dispose()
{
if (!disposed)
{
RemoveProp(handle, myPropertyName);
disposed = true;
}
}
static public bool IsSameUser(IntPtr hWin)
{
if ((int)hWin == 0)
{
return true;
}
else
{
int ptr = GetProp(hWin, myPropertyName);
return (ptr != 0);
}
}
[DllImport("user32", CharSet=CharSet.Auto)]
public extern static int GetProp(IntPtr hwnd, string lpString);
[DllImport("user32", CharSet=CharSet.Auto)]
public extern static bool SetProp(IntPtr hwnd, string lpString, int hData);
[DllImport("user32", CharSet=CharSet.Auto)]
public extern static int RemoveProp(IntPtr hwnd, string lpString);
}
Notes
For the sake of simplicity, the code presented here looks a little different from the sample code. This was done to keep the article brief. Conceptually, they are the same.
References