|
In several different Windows programs that I've developed, I have needed to ensure that only one instance of that application was ever launched at once. Now, working with WinForms application, I ran across the same requirement. After looking through several of the .NET classes, I didn't find the APIs I was used to using in Win32 programming to do this, so I put together a couple of classes (SingleInstanceHelper and WindowsHelper to do just that.
The features of the single instance application are:
- Create a new instance of the app if no other instances are running.
- If another instance is already running, then bring its Window to the foreground.
- When bringing the first instance to the foreground, restore minimized windows to their previous size.
WindowsHelper Code
The WindowsHelper class encapsulates several unmanaged Win32 APIs for dealing with window state and restoration. This class hides most of the complexity of the Win32 APIs and just exposes ActivateWindow method.
using System;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Win32APIs
{
public sealed class WindowsHelper
{
#region structures needed to call into unmanaged Win32 APIs.
[StructLayout(LayoutKind.Sequential)]
private class ManagedPt
{
public int x = 0;
public int y = 0;
public ManagedPt()
{
}
public ManagedPt(int x, int y)
{
this.x = x;
this.y = y;
}
}
[StructLayout(LayoutKind.Sequential)]
private class ManagedRect
{
public int x = 0;
public int y = 0;
public int right = 0;
public int bottom = 0;
public ManagedRect()
{
}
public ManagedRect(int x, int y, int right, int bottom)
{
this.x = x;
this.y = y;
this.right = right;
this.bottom = bottom;
}
}
[StructLayout(LayoutKind.Sequential)]
private class ManagedWindowPlacement
{
public uint length = 0;
public uint flags = 0;
public uint showCmd = 0;
public ManagedPt minPosition = null;
public ManagedPt maxPosition = null;
public ManagedRect normalPosition = null;
public ManagedWindowPlacement()
{
this.length = (uint)Marshal.SizeOf(this);
}
}
#endregion
[DllImport("USER32.DLL", SetLastError=true)]
private static extern uint ShowWindow (uint hwnd, int showCommand);
[DllImport("USER32.DLL", SetLastError=true)]
private static extern uint SetForegroundWindow (uint hwnd);
[DllImport("USER32.DLL", SetLastError=true)]
private static extern uint GetWindowPlacement (uint hwnd,
[In, Out]ManagedWindowPlacement lpwndpl);
[DllImport("USER32.DLL", SetLastError=true)]
private static extern uint FindWindow (string lpClassName, string lpWindowName);
private const int WM_ACTIVATE = 0x0006;
private const int WA_CLICKACTIVE = 2;
private const int SW_SHOWNORMAL = 1;
private const int SW_SHOWMINIMIZED = 2;
private const int SW_SHOWMAXIMIZED = 3;
private const int WPF_RESTORETOMAXIMIZED = 2;
static private uint FindWindow(string windowName)
{
uint hwndInstance = FindWindow(null, windowName);
if (hwndInstance <= 0)
{
Debug.Assert(false,
"Couldn't find window handle for the specified window name.");
}
return hwndInstance;
}
static public void ActivateWindow(string windowName)
{
uint hwndInstance = FindWindow(windowName);
ManagedWindowPlacement placement = new ManagedWindowPlacement();
GetWindowPlacement(hwndInstance, placement);
if (placement.showCmd == SW_SHOWMINIMIZED)
{
int showCmd = (placement.flags == WPF_RESTORETOMAXIMIZED) ?
SW_SHOWMAXIMIZED : SW_SHOWNORMAL;
ShowWindow(hwndInstance, showCmd);
}
else
{
SetForegroundWindow(hwndInstance);
}
}
}
}
|
The first item of interest in the DllImport keyword. DllImport is used to invoke unmanaged APIs that are not COM-based. Although I'm using it to call into a Win32 API, you can also use it to call any legacy C++ DLLs that you have built. For most of the external definitions, we're just passing around basic types like int and string. However, for the GetWindowPlacement method, Windows actually returns a struct with some information.
To account for the struct returned by the Win32 GetWindowPlacement call, I defined a set of managed classes that wrap the data in the corresponding unmanaged structs. As you can see, they contain the exact same public member variables. In addition, they tagged with the [StructLayout(LayoutKind.Sequential)] attribute. This is used to ensure that structured exactly as an unmanaged struct is layed out.
Finally, the ActivateWindow method locates the specified window by its Window name -- this is also known as its caption or text. The FindWindow Windows API also lets you search by window class name. However, WinForms auto-generates the class name (something like WindowsForms10.Window.8.app61), so it may be different between builds. I started trying to search by class name because it seemed like the right way to do it, but after a few different test runs, the class name for the form changed and you couldn't be assured what name to look for. So, I decided to go with the Window name instead.
After finding the corresponding Window handle, we check to see what the current state of the window is (normal, minimized, or maximized). If it's not currently minimized, then we just call SetForegroundWindow to bring the existing Window to the front. If it is minimized, then we check if its previous state was maximized. If it was, then we return the Window to its maximized state; otherwise, we restore it to its previous size and location.
SingleInstanceHelper Code
Now that we have the code for dealing with the native, unmanaged APIs that we need, it's time to look at the code that enforces the single instance nature of the application.
using System;
using System.Collections.Specialized;
using System.Reflection;
using System.Threading;
using System.Windows.Forms;
using Win32APIs;
namespace SingleInstanceApp
{
public sealed class SingleInstanceHelper
{
static private Mutex singleInstance = null;
private const string formatMutexName = "SingleInstanceMutex-{0}-{1}";
static private string UniqueIdentifier
{
get
{
string appName = "";
string appVersion = "0.0.0.0";
Assembly entry = Assembly.GetEntryAssembly();
if (entry != null)
{
AssemblyName assemblyName = entry.GetName();
if (assemblyName != null)
{
appName = assemblyName.Name;
appVersion = assemblyName.Version.ToString();
}
}
return string.Format(formatMutexName, appName, appVersion);
}
}
static public void ProxyMain(Type newFormType, string windowName)
{
singleInstance = new Mutex(false, UniqueIdentifier);
if (singleInstance.WaitOne(1, true))
{
ConstructorInfo constructor = newFormType.GetConstructor(Type.EmptyTypes);
Form newForm = (Form)constructor.Invoke(null);
Application.Run(newForm);
}
else
{
WindowsHelper.ActivateWindow(windowName);
}
}
}
}
|
The bulk of the interesting work is in the ProxyMain method. This method uses a Mutex -- a mutually exclusive semaphore -- to ensure that only one instance of the app is ever created. The first thing it does is attempt to lock the Mutex class by calling the WaitOne method. Notice that this doesn't actually wait at all. If it can't get the lock right away, that means there's a running instance and we return right away to continue with the second option of activating the existing application instance. If it can get a lock, then this must be the first instance, so the main Form for the application is created and the Application.Run method is called -- this is what usually happens in a typical WinForm app's Main method. Also, to make this code more flexible and reusable as a library by multiple programs, the instance of the new form is created using Reflection based on the Type that's passed into this method.
Finally, the UniqueIdentifier property is used to calculate a unique name to use for the Mutex. The name unique for different applications and versions, but otherwise will have the same name. This ensures that the ProxyMain method is looking for the right Mutex for the current app. We get the application name and version number from the Assembly information for this program.
SingleInstanceApp Code
The follow class shows the code changes needed in a simple Form class to make it behave as a single instance application.
using System;
using System.Diagnostics;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
namespace SingleInstanceApp
{
public class FormSingleInstanceApp : System.Windows.Forms.Form
{
private const string windowName = "Single Instance App";
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label labelCurrentProcess;
private System.ComponentModel.Container components = null;
public FormSingleInstanceApp()
{
InitializeComponent();
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
[STAThread]
static void Main(string[] args)
{
SingleInstanceHelper.ProxyMain(typeof(FormSingleInstanceApp), windowName);
}
private void FormSingleInstanceApp_Load(object sender, System.EventArgs e)
{
Process currentProcess = Process.GetCurrentProcess();
if (currentProcess != null)
this.labelCurrentProcess.Text = currentProcess.Id.ToString();
}
}
}
|
The SingleInstanceHelper class is really easy to integrate into the Form code. Basically, you just need to replace the code in the typical Main method with the call to ProxyMain, passing it type of Form you want to create and the window name to look for.
The rest of the typical is just the stuff you need to create a WinForm. I also put in some code in OnLoad to help in debugging by showing the process id of the current running process. This shows that there's ever only one instance and it's the same instance that was first started.
Conclusion
There are many Windows applications that have found it useful to only allow a single instance to ever run. Now, with the code in this tutorial, you can see how that same type of application can be written on the .NET Framework. And, the classes provided in the tutorial can be used as is to add directly into your own application to enable that behavior.
|
|