Introduction
This code contains a class named SingletonApplication
that provides a method of ensuring that only one instance of an application is allowed to load, while at the same time providing a means to use the arguments passed to the second instance in the first instance.
Background
While working on another application, I ran into a situation where I needed only one instance of the application to load, and where I could find out what file the user was trying to open and use that information in the first/main instance of the application. After trying to develop my own solution to no avail, and then searching for a solution to this problem, I came across many different ways to do it, one of which included using the WM_COPYDATA
message to serialize the data and deserialize it in the first instance... I was unable to get this to work in Vista, and decided to make another attempt at writing my own solution for this; however, instead of using memory to transmit the information, I used the Registry.
Using the Code
Below is an example of how SingletonApplication
could be used in your application. Since we will be passing the startup arguments of the second instance in, I separate the code that will use the arguments into a separate method. First, you pass the arguments and an identifier to SingletonApplication.Instance(args, "YOUR-ID-HERE")
; if SingletonApplication.Instance
= true
, that means this is the first instance, and you can proceed as your normally would, making sure to register with the SingletonApplication.SecondInstanceLoaded
event, and if SingletonApplication.Instance
= false
, quit the application (if you want).
using System;
using System.Windows.Forms;
using Azad.Ratzki.Utils;
namespace WindowsFormsApplication1
{
static class Program
{
[STAThread]
static void Main(string[] args)
{
if (SingletonApplication.Instance(args, "ANYTHING-YOU-WANT-HERE"))
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
SingletonApplication.SecondInstanceLoaded += new
EventHandler<SingletonApplicationEventArgs>(
SingletonApplication_SecondInstanceLoaded);
}
else
{
Application.Exit();
}
}
static void SingletonApplication_SecondInstanceLoaded(object sender,
SingletonApplicationEventArgs e)
{
ParseArgs(e.Args);
}
static void ParseArgs(string[] args)
{
}
}
}
Code Explained
SingletonApplication
relies on the Mutex
class to determine whether the instance of the application being loaded is the first or not. For transmitting the arguments to the first instance, I use the built-in Registry .NET classes, and created two methods GetArgs()
and SetArgs(string[] args)
to store and retrieve the arguments from the Registry. Finally, to nudge the first instance and get it to retrieve the arguments, I use a NativeWindow
derived class called SpecialNativeWindow
to catch a window message, causing an event to bubble up to the SingletonApplication
class, where we call GetArgs()
and pass the retrieved string
array into a new SingletonApplicationEventArgs
and fire the SecondInstanceLoaded
event.
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
using Microsoft.Win32;
namespace Azad.Ratzki.Utils
{
class SingletonApplication : IDisposable
{
#region PInvoke
[DllImport("user32.dll", EntryPoint =
"FindWindow", SetLastError = true)]
static extern System.IntPtr FindWindowByCaption(int ZeroOnly,
string lpWindowName);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool SendNotifyMessage(IntPtr hWnd, int Msg,
IntPtr wParam, IntPtr lParam);
#endregion
#region Events
public static event
EventHandler<SingletonApplicationEventArgs> SecondInstanceLoaded;
#endregion
#region Fields
SpecialNativeWindow sw;
static SingletonApplication _instance;
static Mutex myMutex;
static string appID;
#endregion
#region Constants
const int CMESSAGE = 93956;
#endregion
#region Constructor
SingletonApplication() { }
#endregion
#region Methods
public static bool Instance(string[] args, string sharedID)
{
_instance = new SingletonApplication();
SetArgs(args);
bool owned = false;
myMutex = new Mutex(true, sharedID, out owned);
appID = sharedID;
GC.KeepAlive(myMutex);
if (owned)
{
_instance.sw = new SpecialNativeWindow(sharedID);
_instance.sw.ApplicationOpened +=
new EventHandler<EventArgs>(sw_ApplicationOpened);
GC.KeepAlive(_instance);
GC.KeepAlive(_instance.sw);
}
else
{
IntPtr foundWin = FindWindowByCaption(0, sharedID);
SendNotifyMessage(foundWin, CMESSAGE, IntPtr.Zero, IntPtr.Zero);
}
return owned;
}
static void sw_ApplicationOpened(object sender, EventArgs e)
{
_instance.OnSecondInstanceLoaded();
}
protected void OnSecondInstanceLoaded()
{
if (SecondInstanceLoaded != null)
{
SecondInstanceLoaded(this, new SingletonApplicationEventArgs(GetArgs()));
}
}
static string[] GetArgs()
{
try
{
string[] tempString = (string[])
Registry.CurrentUser.OpenSubKey(@"Software\" +
appID, false).GetValue("Args", null);
Registry.CurrentUser.OpenSubKey("Software",
true).DeleteSubKey(appID);
return tempString;
}
catch
{
return null;
}
}
static void SetArgs(string[] args)
{
Registry.CurrentUser.OpenSubKey("Software",
true).CreateSubKey(appID).SetValue("Args", args);
}
#endregion
#region MessengerWindow
class SpecialNativeWindow : NativeWindow
{
public event EventHandler<EventArgs> ApplicationOpened;
private void OnApplicationOpened()
{
if (ApplicationOpened != null)
{
ApplicationOpened(this, EventArgs.Empty);
}
}
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case CMESSAGE:
OnApplicationOpened();
break;
default:
break;
}
base.WndProc(ref m);
}
public SpecialNativeWindow(string ID)
{
CreateParams cp = new CreateParams();
cp.Caption = ID;
this.CreateHandle(cp);
}
}
#endregion
#region IDisposable Members
public void Dispose()
{
if (myMutex != null)
{
myMutex.ReleaseMutex();
}
if (_instance.sw != null)
{
_instance.sw.DestroyHandle();
_instance.sw = null;
}
_instance = null;
}
#endregion
}
class SingletonApplicationEventArgs : EventArgs
{
public string[] Args { get; set; }
public SingletonApplicationEventArgs(string[] args)
{
Args = args;
}
}
}
Points of Interest
It was interesting and kind of puzzling why this problem wasn't more discussed than it is. My problem was trying to pass an open file argument to the first instance of an application and being unable to find an easy, established way to do this. I would also like to mention that I know this could have been done much more cleanly and that the above code could be much improved upon, and if anybody has any suggestions/comments, I would greatly appreciate them.
History
Posted first version.