A C# Windows Forms application that allows only one instance

A common requirement for application developers is an application that allows only a single instance. I can still remember the first time it came up for me. That was way back in the year 1999 – and I was a Delphi developer at the time. After diving into the middle of one of the “Mastering Delphi” books for two days, with no previous programming experience, having studied heavy current electrical engineering, I’d somehow managed to convince management to give me a chance as a developer. (I still don’t know how I got that right.) Those were fun times. I knew nothing about development, except that it fascinated me. I think it was nearly a week before I “discovered” that I could create my own classes. But maybe I’ll write about that another time.

The reason I’ve brought up my junior developer days is that, if you got here via a search on how to allow only one application instance, you probably haven’t been developing for long, and you might not ask yourself this important question: Is it really necessary? Preventing multiple instances is a step into the realm of taking control away from the user. Never fool yourself into thinking you are in control. Good software doesn’t dictate to it’s users how to behave. People use it because it does what they want, and many users, myself included, are quite ruthless about removing software they find annoying.

Of course there are often legitimate reasons for limiting the application to a single instance, and all the code presented here can be found in the zip file below.

RomyView source code zip file

My requirements for a single instance application

  1. Only one instance is allowed to run. (Of course.)
  2. The first instance must instantiate a form, the type of which is specified somehow.
  3. Any subsequent instances launched should do as follows:
    1. Do its best to select the existing instance and bring it to the front of the Z-order, even if it is minimized.
    2. Pass on any command-line arguments, if it was launched with arguments.
    3. In the event of the existing instance not responding, the new instance should not freeze; it should time out gracefully.
    4. Exit, regardless of whether the existing instance responded.

The implementation

The universally accepted pattern to do this is to use a named system mutex, which then coordinates the access between the different processes. The logical way to implement this, in my mind, is to use an abstract class, with a generic type constraint and a Name property. This way, it’s easy to do all the work in the base class, making the derived type very simple to write. (Almost no code at all, and the type that will be instantiated as well as the name of the system mutex is baked into the design).

Here is my abstract base class:

  1. public abstract class SingleInstance<T> where T : Form
  2. {
  3.     /// <summary>When implemented in a derived class, indicates a unique name used to identify the instance.</summary>
  4.     /// <remarks>Internally, this sets the name of a <b>named system mutex</b> that is used to coordinate access to the
  5.     /// application between processes.</remarks>
  6.     public abstract string Name
  7.     {
  8.         get;
  9.     }
  10.  
  11.     /// <summary>When implemented in a derived class, specifies any application-specific
  12.     /// action to perform with the commandline arguments passed to the application.</summary>
  13.     /// <remarks>This method is called before creating the first instance, so you can’t rely
  14.     /// on any data structures that are created by your forms.</remarks>
  15.     public virtual void ProcessCommandlineArguments(string[] args) { }
  16.  
  17.     [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.ControlAppDomain)]
  18.     public virtual void Run()
  19.     {
  20.         var createdNew = true;
  21.         var args = Environment.GetCommandLineArgs();
  22.  
  23.         using (var mutex = new Mutex(true, Name, out createdNew))
  24.         {
  25.             // If this instance owns the mutex, run normally.
  26.             if (createdNew)
  27.             {
  28.                 ProcessCommandlineArguments(args);
  29.  
  30.                 // Hook up my exception logging helper class
  31.                 ApplicationExceptionLogger.SetExceptionLoggers();
  32.  
  33.                 Application.EnableVisualStyles();
  34.                 Application.SetCompatibleTextRenderingDefault(false);
  35.  
  36.                 Application.Run(typeof(T).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null).Invoke(null) as Form);
  37.             }
  38.             else
  39.             {
  40.                 // An instance is already running. Find it and try to activate it.
  41.                 Process current = Process.GetCurrentProcess();
  42.  
  43.                 foreach (var process in Process.GetProcessesByName(current.ProcessName))
  44.                 {
  45.                     if (process.Id != current.Id)
  46.                     {
  47.                         if (NativeMethods.IsIconic(process.MainWindowHandle))
  48.                             NativeMethods.ShowWindowAsync(process.MainWindowHandle, NativeConstants.SW_RESTORE);
  49.  
  50.                         if (NativeMethods.SetForegroundWindow(process.MainWindowHandle))
  51.                             NativeMethods.SetActiveWindow(process.MainWindowHandle);
  52.  
  53.                         // Pass on any commandline arguments via a WM_COPYDATA message.
  54.                         if (args.Length > 0)
  55.                         {
  56.                             /* Note that process.MainWindowHandle is actually the handle of the currently ACTIVE form (maybe not
  57.                              * the main form at all), so all forms that may be active need to be able to handle this message. */
  58.  
  59.                             // Pass the arguments on, after combining them into a single, comma-separated string.
  60.                             DataCopyWrapper.Send_WM_COPYDATA(process.MainWindowHandle, current.Handle, string.Join(“,”, args));
  61.  
  62.                             // See Romy.Application.Browser.WndProc for an example of receiving and processing this message.
  63.                         }
  64.                         break;
  65.                     }
  66.                 }
  67.             }
  68.         }
  69.     }
  70. }

Don’t worry about ApplicationExceptionLogger. That’s a flaky logging class of my own, and not necessary here. The optional to implement (virtual) ProcessCommandLineArguments method is also unnecessary fluff that I added to be able to send some arguments to be processed before instantiating the application.

The flow is straightforward. The first process will always own the mutex. It then creates the Form, with code almost identical to the code normally generated in an application’s program.cs file, except that it needs to use reflection to instantiate the form.

A subsequent process will not own the mutex, so it finds the first instance, and tries to activate it. Then, if it has any command line arguments to pass along, it does so by sending a WM_COPYDATA message. There is a good example of sending as well as receiving such messages on the Microsoft All-In-One Code Framework, in fact that is where my code to handle these messages comes from. There is however, a risk when sending the message. It has to be sent via the Windows API SendMessage function, and this function does not return until the receiver processes it. Of course, the implication of this is, if the first instance is frozen, and receives but never processes the message, the second instance will also freeze as it waits forever on the blocking call. Oops.

To work around this, I made a small change to the code that sends the WM_COPYDATA message. I made the method async, then offloaded the call to an asynchronous Task, via Task.Run. I then time out the call, if it takes longer than ten seconds. The extension method I use to time out the call, as well as loads of other goodies, can be found on the Parallel Programming with .Net blog.

  1. /// <summary>Sends a string to the specified main window via a WM_COPYDATA message.</summary>
  2. /// <param name=”mainWindowHandle”>The main window handle of the program to which the message will be sent.</param>
  3. /// <param name=”handle”>The process handle of the program sending the message.</param>
  4. /// <param name=”message”>The string to be sent to the program.</param>
  5. public static async void Send_WM_COPYDATA(IntPtr mainWindowHandle, IntPtr handle, string message)
  6. {
  7.     ManagedCopyDataStruct dataStruct = new ManagedCopyDataStruct(message);
  8.  
  9.     // Marshal the managed struct to a native block of memory.
  10.     int dataStructSize = Marshal.SizeOf(dataStruct);
  11.     IntPtr pDataStruct = Marshal.AllocHGlobal(dataStructSize);
  12.  
  13.     try
  14.     {
  15.         Marshal.StructureToPtr(dataStruct, pDataStruct, true);
  16.  
  17.         COPYDATASTRUCT cds = new COPYDATASTRUCT();
  18.         cds.cbData = dataStructSize;
  19.         cds.lpData = pDataStruct;
  20.  
  21.         await Task.Run(() =>
  22.         {
  23.             /* Send the COPYDATASTRUCT struct through the WM_COPYDATA message to
  24.              * the receiving window. (The application must use SendMessage,
  25.              * instead of PostMessage to send WM_COPYDATA because the receiving
  26.              * application must accept while it is guaranteed to be valid.) */
  27.             NativeMethods.SendMessage(mainWindowHandle, DataCopyWrapper.WM_COPYDATA, handle, ref cds);
  28.         }).TimeoutAfter(TimeSpan.FromSeconds(10D));
  29.  
  30.         /* To prevent this instance freezing if the receiver is frozen
  31.          * and never processes the message, time out after a bit. */
  32.     }
  33.     finally
  34.     {
  35.         Marshal.FreeHGlobal(pDataStruct);
  36.     }
  37. }

Above, is the slightly altered call to SendMessage. Thanks to Microsoft’s parallel programming team’s awesome work, a potential disaster became trivial to solve.

Below are the Windows API functions that this code needed to access via platform invoke.

  1. internal static class NativeMethods
  2. {
  3.     [DllImport(“user32.dll”)]
  4.     public static extern bool IsIconic(IntPtr hWnd);
  5.  
  6.     [DllImport(“user32.dll”)]
  7.     public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
  8.  
  9.     [DllImport(“user32.dll”)]
  10.     [return: MarshalAs(UnmanagedType.Bool)]
  11.     internal static extern bool SetForegroundWindow(IntPtr hWnd);
  12.  
  13.     [DllImport(“user32.dll”)]
  14.     internal static extern IntPtr SetActiveWindow(IntPtr hWnd);
  15. }

 

With all the work done by the base class, here is my application’s derived class. Note that it is mostly taken up by the unnecessary ProcessCommandLineArguments method. All that was really necessary here was to define the Name property. The type of form to instantiate (Splash) is also of course specified, since it is a type constraint in the abstract base class, and thus is specified in the signature of the derived class.

Normally, the type of form would indicate the main of the application, but in this case, I pass it the type of my splash screen, which is then responsible for instantiating my real main form.

Since all the work normally done in the generated program.cs file has been done manually, this also results in the Main method in program.cs now being only one line: new Romy.UI.Instance().Run();

  1. class Instance : SingleInstance<Splash>
  2. {
  3.     public override string Name
  4.     {
  5.         get { return “RomyView”; }
  6.     }
  7.  
  8.     public override void ProcessCommandlineArguments(string[] args)
  9.     {
  10.         if (args.Length > 1)
  11.         {
  12.             foreach (var arg in args)
  13.             {
  14.                 if ((Directory.Exists(arg) || (File.Exists(arg)) && Romy.Core.IO.SupportsFile(arg)))
  15.                 {
  16.                     Romy.Core.Properties.Settings.Default.Argument = arg;
  17.                 }
  18.             }
  19.         }
  20.     }
  21. }
Advertisements

About Jerome

I am a senior C# developer in Johannesburg, South Africa. I am also a recovering addict, who spent nearly eight years using methamphetamine. I write on my recovery blog about my lessons learned and sometimes give advice to others who have made similar mistakes, often from my viewpoint as an atheist, and I also write some C# programming articles on my programming blog.
This entry was posted in Programming and tagged , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s