Introduction
This article shows yet another technique on how to include support for activating an already running instance of your application when a user tries to start a new instance.
The differences between this technique and others I've found on The Code Project are:
- Support for scenarios where you sometimes want to start a new instance even if an existing instance is already running
- Send data to the existing application when a new instance tries to start
To understand the implementation, the article discusses the following subjects:
- Using a mutex to determine if the application is already running
- Using .NET
NativeWindow
class to implement a Windows message loop without requiring a Form - Sending messages between applications using
WM_COPYDATA
.
Background
The class described here is used in an open source project called Terminals that me and a friend are working on. The project is a multi-tabbed RDP client that can be found here.
The Terminals application allows you to pass a server name on the command line so it can connect to it as soon as the application starts (i.e. Terminals.exe Server1). One of our users requested that if a new instance is started with a command line (i.e. Terminals.exe server2) that the new connection will be opened in a new tab instead of starting a new process. We also wanted to still support a scenario where a user starts a new instance.
Note: This works exactly the way Internet Explorer 7 does. If you enter the URL of a site in the Start->Run command, it will open in a new tab if Internet Explorer is already running, but you can still start new instances if you start Internet Explorer directly from its shortcut.
Using the Code
The class is implemented as a singleton with static
methods for ease of use. Start by calling SingleInstanceApplication.NotifyExistingInstance().
This will check if another instance already exists and if so, it will send it the data passed to the function. The data can be any .NET object that is serializable.
if (SingleInstanceApplication.NotifyExistingInstance("Some data"))
return;
Next, if another instance does not already exist, you should call the Initialize()
method:
SingleInstanceApplication.Initialize();
If you want to get notifications when a new instance is started, subscribe to the NewInstanceMessage
event:
{
SingleInstanceApplication.NewInstanceMessage += new NewInstanceMessageEventHandler(
SingleInstanceApplication_NewInstanceMessage);
}
void SingleInstanceApplication_NewInstanceMessage(object sender,
object message)
{
}
The Details
Ok, let's dive into the implementation details, taking one step at a time.
Determining If Another Instance is Already Running
The easiest way to determine if another instance is already running is simply to create a Mutex
object when the application starts. The nice thing about Mutex
es is that when you create them, you can:
- Give them a name so they will be accessible from more than 1 process.
- Provide an
out
parameter to their constructor that will be true
if this is the first instance if the mutex
.
So basically, we do this:
_instanceCounter = new Mutex(false, _id, out _firstInstance);
_Id
is the name of the Mutex
which is actually the name of the application EXE. _firstInstance
will be set to true
if this is the first instance of the Mutex
. Notice that the first parameter to the Mutex
constructor determines whether to lock the mutex
. We don't want to lock it - we only care whether it already exists or not.
Notifying the Existing Instance that Another Instance was Started
Great. So now whenever a new instance starts, it checks the _firstInstance
variable and knows if another instance was already started. If so, we need to notify the other instance and possibly provide it with some information. (In the case of the Terminals project mentioned above, we needed to pass the command line parameters we received).
So how do we notify the other window? I've seen some solutions using Remoting, some using shared memory and some ignoring the problem altogether. I decided to use the simplest Windows mechanism to communicate between processes: Windows Messages.
The idea is to create our own hidden window that will have a title with a unique id that we can identify and send a message to that window. How do you create a hidden window in .NET without creating a Form? Simple: Use the NativeWindow
class.
What you need to do is create a subclass of NativeWindow
and override its WndProc()
method:
class SIANativeWindow : NativeWindow
{
public SIANativeWindow()
{
CreateParams cp = new CreateParams();
cp.Caption = _theInstance._id;
CreateHandle(cp);
}
protected override void WndProc(ref Message m)
{
}
}
So, we have a hidden window that can accept messages. Which message should we send it? Since we need to pass data and not the usual Int
, Long
parameters that are usually passed with messages, the perfect fit is WM_COPYDATA. This message was created exactly for that purpose - to pass data between applications.
Sending Data between Applications using WM_COPYDATA
Let's get to work then. The first thing we need to do is find our hidden window:
IntPtr handle = NativeMethods.FindWindow(null, _id);
We serialize the object we want to send to a byte array and now we need to fill the WM_COPYDATA
structures with the correct information. Since we are using native Windows APIs to send the message and since our byte array is a .NET object, we first need to Pin our object in memory and get a "native" pointer to it:
bufferHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
...
data.lpData = bufferHandle.AddrOfPinnedObject();
And now we just need to send the data to our window:
NativeMethods.SendMessage
(handle, NativeMethods.WM_COPYDATA, IntPtr.Zero, dataHandle.AddrOfPinnedObject());
Handling the WM_COPYDATA on the Other End of the Pipe
Ok now - The application was found, the window was found, the message was sent. The last thing we have to do is actually handle that message in the hidden window we created above.
Again, since the message arrives as a native WM_COPYDATA
structure, we first have to convert it to a .NET byte array and then deserialize the original object.
COPYDATASTRUCT data = (COPYDATASTRUCT)Marshal.PtrToStructure(m.LParam,
typeof(COPYDATASTRUCT));
byte[] buffer = new byte[data.cbData];
Marshal.Copy(data.lpData, buffer, 0, buffer.Length);
obj = Deserialize(buffer);
We now have a copy of the object that was sent from the new application instance. We can activate our window, do something with the information passed to us (e.g. create a new tab and connect to another server as in the Terminals application).
Conclusion
This class allows you create a "Singleton" application and pass information between new instances of the application and existing instances. It uses very simple mechanisms to determine if the application is already running (Mutex
) and to communicate between applications (Windows messages).
Links
History
- 14th March, 2007: Initial post
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.