Running a Single Instance per User






2.69/5 (11 votes)
How to run one instance per user of an application on a machine with multiple users logged in.
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:
// The main entry point to the application
[STAThread]
static void Main()
{
// Look for a running instance
Process runningInstance = GetRunningInstance();
if (runningInstance == null)
{
// Create the user instance property and
// set it on the main form
Form mainForm = new Form1();
UserInstance user = new UserInstance(mainForm.Handle);
// Run the application
Application.Run(mainForm);
// dispose of the user instance
user.Dispose();
}
else
{
// There is another instance of this process;
// show it instead
ShowWindow (runningInstance.MainWindowHandle, 1);
}
}
public static Process GetRunningInstance()
{
// Get the current process and all processes
// with the same name
Process current = Process.GetCurrentProcess();
Process[] processes =
Process.GetProcessesByName(current.ProcessName);
//Loop through the running processes with the same name
foreach (Process process in processes)
{
// Looking for a process with a different ID and
// the same username
if ((process.Id != current.Id) &&
UserInstance.IsSameUser(process.MainWindowHandle))
{
//Return the other process instance.
return process;
}
}
return null;
}
// Import DLL methods from user32
[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)
{
// remove the property
RemoveProp(handle, myPropertyName);
disposed = true;
}
}
static public bool IsSameUser(IntPtr hWin)
{
if ((int)hWin == 0)
{
// If the main window has not been started yet
// then the user clicked too many times.
return true;
}
else
{
// If the property does not exist then this
// is a different user
int ptr = GetProp(hWin, myPropertyName);
return (ptr != 0);
}
}
// Import DLL methods from user32
[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);
}//end class
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
- Using Window Properties - A link to the MSDN library description for how to implement windows properties
- Platform Invoke Tutorial - How to use
DllImport
to access theUser32
Windows API