Sunday, March 06, 2005
You Only Run Once
You have probably seen how applications such as Media Player respond when already running and you try to open another copy. A typical scenario might be to have a song playing in Media Player and then you double-click another song file in your music library. It wouldn't be appropriate for Media Player to open a second copy. You only have one set of sound hardware and the running copy already has control of it.
The more appropriate behavior, which is coincidentally the actual behavior, is that when you double-click the second file, a new copy of Media Player begins to open and checks for, and finds, an existing running instance. The second copy of Media Player passes its command line to the previous instance and the previous instance responds appropriately by stoppng the play on the current track and opening the track you, the user, double-clicked to play.
Another example would be an application that requires the use of a specific TCP/IP port. If one instance is already running and using the port, the second instance would fail to get the port. In order to run, and therefore to access the required TCP/IP port, write your application so that it must have your application's mutex.
This article will show you how you can provide the same behavior in your own applications. It describes how to ensure that only one copy of your Windows application is running and how to pass the CommandLine from a new instance to the already running instance before closing the new instance. I haven't tried to include the complete source in this article because it is just too long. If you want to see it all, download the demo source code.
Beyond the scope of this article is how to associate file types with your application so that double-clicking the application will create the command line. For our purposes here, the command line is created manually by typing, in a command window
YouOnlyRunOnce.exe C:\Tips.txt
The key classes and functions required to ensure there is only one instance running and to pass a message to that instance are:
- DllImport - for accessing Win32 API functions
- System.Threading.Mutex - A synchronization mechanism built into Windows
- SendMessage - for passing Windows messages to your application
- WM_COPYDATA - the Windows message we will use in this example
- COPYDATASTRUCT - a Win32 defined structure for sharing data between processes
The key to making sure that your application only runs once is using the System.Threading.Mutex class. Mutexes can be thought of like a token. To use resources associated with the token, you must be holding the token. In an auditorium full of people, in order to address the room you must hold the portable microphone. The microphone is the token in this case, and holding it gives you access to the resource.
In Windows, there can be many mutexes created at any given time but only one mutex may be created with a specific given name. If your application requires ownership of a specific mutex to run, then if it can't get the mutex it will not run.
Mutexes are created like this:
bool mutexCreated;
System.Threading.Mutex mutex = new System.Threading.Mutex(true,"9F2A6412-6339-43bf-94d6-D73520a74227", out mutexCreated);
The parameters to the constructor are:
- true - indicating that the currente thread should immediately take ownership of the mutex when it is created
- a GUID name for the mutex. The mutex name can be any unique string name but I recommend using a GUID. There are too many developers making too many applications and naming them from too few real dictionary words to count on your application name always being unique. Imagine the frustration of a user (customer) who cannot run your application because they have some other totally unrelated application running on their PC. Using a GUID ensures that only instances of your own application will prevent a second instance from running.
- out mutexCreated - the last parameter is a boolean value that the constructor sets to indicate whether the mutex was successfully created. If true, then our application instance is holding the mutex and can execute. If false, then another application instance is holding the mutex and our instance must shut down.
Test for successful creation of the mutex and, if true, run the form or, if false, don't run it:
if (mutexCreated)
{
Application.Run(new Form1());
GC.KeepAlive(mutex);
}
else
{
Form1.SendMessageToRunning();
}
Notice the static method call to Form1.SendMessageToRunning() if the mutex creation fails. At this point in the application startup, still in main() and we haven't called Application.Run on the form, the form does not exist and instance methods cannot be called. You could make your message handling code instance methods and call them from the form's Load event or you could even put them all into a separate class library. For demonstration purposes here, I chose to make the methods static methods of the Form1 class.
So what does Form1.SendMessageToRunning() do? It starts a chain of events that will result in the command line from the current application instance being sent, via Win32 API calls, to the previous instance of the application.
When the initial application instance starts, I add a property to the window, using SetProp(), to identify the application uniquely. This property will help us identify the initial instance of our application so that we can send the new command line to the correct window.
I set the property name to the same GUID string that I used to name my mutex. This saves several method calls by naming the property with the value. I could have named the property, for instance, "AppID" and give the property a value that is a pointer to the GUID. Then to determine if the process is an instance of my application I have to retrieve the value of the property, retrieve the data stored at the location referred to by the pointer, and compare that to my GUID. I used the GUID here for the property name for the same reasons as I used a GUID to name my mutex. The GUID makes sure that some other developer doesn't use the same dictionary words as me and cause conflicts later on.
I set the property by adding the following to the Form.Load event handler:
// Tag this window with a GUID property and a value of this window's handle
if (Win32.SetProp(
this.Handle,
"9F2A6412-6339-43bf-94d6-D73520a74227",
(int)this.Handle)
== 0)
{
throw new ApplicationException("Could not set AppID.");
}
Note that the Win32 class referenced in the SetProp method call is a private class I created to hold all my API method declarations.
To send the command line to the previous instance, the current instance loops through all open Windows. To see this code, download the demo application source for this article. When it finds our previous instance, the current instance calls upon the SendMessage Win32 API function using the WM_COPYDATA message type and the COPYDATASTRUCT structure to hold the command:
/// <summary>
/// Sends the command line string to the previous instance for processing
/// </summary>
private static void SendMessageToRunning()
{
Form1.GetRunningInstance();
IntPtr ptr = Marshal.StringToHGlobalAnsi(Environment.CommandLine);
Win32.COPYDATASTRUCT cds = new Win32.COPYDATASTRUCT();
cds.dwData = IntPtr.Zero;
cds.cbData = Environment.CommandLine.Length;
cds.lpData = ptr;
long result = Win32.SendMessage(Form1.prevPtr,WM_COPYDATA,0,ref cds);
// Required to free unmanaged memory otherwise a memory leak occurs
Marshal.FreeHGlobal(ptr);
The last things that have to happen are for the original instance to receive the WM_COPYDATA message and then to extract the command line and do something with it.
Receiving the WM_COPYDATA message has never been easier than it is in the .Net framework. All that is required is to override the WndProc() method for our form and add a handler for the WM_COPYDATA message:
protected override void WndProc(ref Message m)
{
// Listen for operating system messages.
switch (m.Msg)
{
// Listen for WM_COPYDATA
case WM_COPYDATA:
string strCmdLine = string.Empty;
Win32.COPYDATASTRUCT cds = new Win32.COPYDATASTRUCT();
cds = (Win32.COPYDATASTRUCT) Marshal.PtrToStructure(m.LParam, typeof(Win32.COPYDATASTRUCT));
if (cds.cbData > 0)
{
strCmdLine = Marshal.PtrToStringAnsi(cds.lpData).Substring(0,cds.cbData);
// This section attempts to extract the filename to open from the
// command line arguments. This is tricky because of the multitude
// of ways the OS can handle quotes and spaces in commandlines.
// While this worked for me for the purposes of this demonstration
// it may not work for your commands. Parsing the command line
// is beyond the scope of this demonstration so if this doesn't work
// for you, you will have to work out your own means of parsing the
// command line.
// Of course, if you have a better way, I'd love to hear from you so I can
// update both this article and my skills.
if (strCmdLine.StartsWith("\""))
{
strCmdLine = strCmdLine.Substring(strCmdLine.IndexOf("\"",1));
}
else if (strCmdLine.IndexOf("\"") > 0)
{
int firstIndex = strCmdLine.IndexOf("\"");
int lastIndex = strCmdLine.LastIndexOf("\"");
strCmdLine = strCmdLine.Substring(firstIndex+1,lastIndex-firstIndex-1);
}
else if (strCmdLine.LastIndexOf(" ") > 0)
{
strCmdLine = strCmdLine.Substring(strCmdLine.LastIndexOf(" ")+1).Trim();
}
else
{
strCmdLine = string.Empty;
}
if (strCmdLine.Trim().Length > 0)
{
// Display the file passed to the second instance
this.DisplayFileContent(strCmdLine);
}
// bring this instance to the front
this.Activate();
}
break;
}
// make sure to call the base.WndProc or no other
// messages will get handled
base.WndProc(ref m);
}
Comments:
<< Home
All i can say is thanks a million Dale.You saved my day.I was struggling with this marshalling thing for the past few days.
I was converting the structure to a pointer and attempting to send it across.
Post a Comment
I was converting the structure to a pointer and attempting to send it across.
<< Home


