Single Instance Application Manager

13 בפברואר 2010

Some .Net applications are required to run as single instance process (at startup, each process “ensures” that he’s unique). This requirement may come from licensing issues, technical and/or other reasons. Quick search in Google will provide a variety of solutions, most of them are based on WindowsFormsApplicationBase object from VB.Net, usage of Mutex object and other techniques. I don’t like mixing VB with C#, and also Win-Forms solution isn’t “pure” enough for WPF applications. So I wrote my version of “Single Instance Manager”…

This post will provide short description about this small project and links to download the sources.

How to Create Single Instance Application for WPF, Win-Forms and Console Applications

The manager uses well-known ThreadPool object with WaitEventHandle object and IpcClientChannel to pass messages (objects) between processes (.Net Remoting).

Diagram: Pass object cross processes

[In Diagram: Instance A passes Console Arguments to Process B, before it closes it self]

Each application will use “ApplicationInstanceManager.CreateSingleInstance(…)” to register it self as single instance application.

image

[In Diagram: Description of “CreateSingleInstance(…)” Function]

The manager ensures that application has only one instance and also passes console arguments from new instances to running process, before “killing” them.

   1:  using System;
   2:  using System.Diagnostics;
   3:  using System.Runtime.Remoting;
   4:  using System.Runtime.Remoting.Channels;
   5:  using System.Runtime.Remoting.Channels.Ipc;
   6:  using System.Threading;
   7:   
   8:  namespace SingleInstanceApplication
   9:  {
  10:      /// <summary>
  11:      /// Application Instance Manager
  12:      /// </summary>
  13:      public static class ApplicationInstanceManager
  14:      {
  15:          /// <summary>
  16:          /// Creates the single instance.
  17:          /// </summary>
  18:          /// <param name="name">The name.</param>
  19:          /// <param name="callback">The callback.</param>
  20:          /// <returns></returns>
  21:          public static bool CreateSingleInstance(string name, EventHandler<InstanceCallbackEventArgs> callback)
  22:          {
  23:              EventWaitHandle eventWaitHandle = null;
  24:              string eventName = string.Format("{0}-{1}", Environment.MachineName, name);
  25:   
  26:              InstanceProxy.IsFirstInstance = false;
  27:              InstanceProxy.CommandLineArgs = Environment.GetCommandLineArgs();
  28:   
  29:              try
  30:              {
  31:                  // try opening existing wait handle
  32:                  eventWaitHandle = EventWaitHandle.OpenExisting(eventName);
  33:              }
  34:              catch
  35:              {
  36:                  // got exception = handle wasn't created yet
  37:                  InstanceProxy.IsFirstInstance = true;
  38:              }
  39:   
  40:              if (InstanceProxy.IsFirstInstance)
  41:              {
  42:                  // init handle
  43:                  eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName);
  44:   
  45:                  // register wait handle for this instance (process)
  46:                  ThreadPool.RegisterWaitForSingleObject(eventWaitHandle, WaitOrTimerCallback, callback, Timeout.Infinite, false);
  47:                  eventWaitHandle.Close();
  48:   
  49:                  // register shared type (used to pass data between processes)
  50:                  RegisterRemoteType(name);
  51:              }
  52:              else
  53:              {
  54:                  // pass console arguments to shared object
  55:                  UpdateRemoteObject(name);
  56:   
  57:                  // invoke (signal) wait handle on other process
  58:                  if (eventWaitHandle != null) eventWaitHandle.Set();
  59:   
  60:   
  61:                  // kill current process
  62:                  Environment.Exit(0);
  63:              }
  64:   
  65:              return InstanceProxy.IsFirstInstance;
  66:          }
  67:   
  68:          /// <summary>
  69:          /// Updates the remote object.
  70:          /// </summary>
  71:          /// <param name="uri">The remote URI.</param>
  72:          private static void UpdateRemoteObject(string uri)
  73:          {
  74:              // register net-pipe channel
  75:              var clientChannel = new IpcClientChannel();
  76:              ChannelServices.RegisterChannel(clientChannel, true);
  77:   
  78:              // get shared object from other process
  79:              var proxy =
  80:                  Activator.GetObject(typeof(InstanceProxy), 
  81:                  string.Format("ipc://{0}{1}/{1}", Environment.MachineName, uri)) as InstanceProxy;
  82:   
  83:              // pass current command line args to proxy
  84:              if (proxy != null)
  85:                  proxy.SetCommandLineArgs(InstanceProxy.IsFirstInstance, InstanceProxy.CommandLineArgs);
  86:   
  87:              // close current client channel
  88:              ChannelServices.UnregisterChannel(clientChannel);
  89:          }
  90:   
  91:          /// <summary>
  92:          /// Registers the remote type.
  93:          /// </summary>
  94:          /// <param name="uri">The URI.</param>
  95:          private static void RegisterRemoteType(string uri)
  96:          {
  97:              // register remote channel (net-pipes)
  98:              var serverChannel = new IpcServerChannel(Environment.MachineName + uri);
  99:              ChannelServices.RegisterChannel(serverChannel, true);
 100:   
 101:              // register shared type
 102:              RemotingConfiguration.RegisterWellKnownServiceType(
 103:                  typeof(InstanceProxy), uri, WellKnownObjectMode.Singleton);
 104:   
 105:              // close channel, on process exit
 106:              Process process = Process.GetCurrentProcess();
 107:              process.Exited += delegate { ChannelServices.UnregisterChannel(serverChannel); };
 108:          }
 109:   
 110:          /// <summary>
 111:          /// Wait Or Timer Callback Handler
 112:          /// </summary>
 113:          /// <param name="state">The state.</param>
 114:          /// <param name="timedOut">if set to <c>true</c> [timed out].</param>
 115:          private static void WaitOrTimerCallback(object state, bool timedOut)
 116:          {
 117:              // cast to event handler
 118:              var callback = state as EventHandler<InstanceCallbackEventArgs>;
 119:              if (callback == null) return;
 120:   
 121:              // invoke event handler on other process
 122:              callback(state,
 123:                       new InstanceCallbackEventArgs(InstanceProxy.IsFirstInstance,
 124:                                                     InstanceProxy.CommandLineArgs));
 125:          }
 126:      }
 127:  }

For testing purposes I added form with grid that allows starting processes with console arguments:

single instance manager

How can we use this manager (for example in WPF app):

   1:  using System;
   2:  using System.Reflection;
   3:  using System.Windows;
   4:   
   5:  namespace SingleInstanceApplication.WpfApp
   6:  {
   7:      /// <summary>
   8:      /// Interaction logic for App.xaml
   9:      /// </summary>
  10:      public partial class App
  11:      {
  12:          /// <summary>
  13:          /// Raises the <see cref="E:System.Windows.Application.Startup"/> event.
  14:          /// </summary>
  15:          /// <param name="e">A <see cref="T:System.Windows.StartupEventArgs"/> that contains the event data.</param>
  16:          protected override void OnStartup(StartupEventArgs e)
  17:          {
  18:              // register single instance app. and check for existence of other process
  19:              if (!ApplicationInstanceManager.CreateSingleInstance(
  20:                      Assembly.GetExecutingAssembly().GetName().Name,
  21:                      SingleInstanceCallback)) return; // exit, if same app. is running
  22:   
  23:              base.OnStartup(e);
  24:          }
  25:   
  26:          /// <summary>
  27:          /// Raises the <see cref="E:System.Windows.Application.Activated"/> event.
  28:          /// </summary>
  29:          /// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.</param>
  30:          protected override void OnActivated(EventArgs e)
  31:          {
  32:              base.OnActivated(e);
  33:   
  34:              var win = MainWindow as MainWindow;
  35:              if (win == null) return;
  36:   
  37:              // add 1st args
  38:              win.ApendArgs(Environment.GetCommandLineArgs());
  39:          }
  40:   
  41:          /// <summary>
  42:          /// Single instance callback handler.
  43:          /// </summary>
  44:          /// <param name="sender">The sender.</param>
  45:          /// <param name="args">The <see cref="SingleInstanceApplication.InstanceCallbackEventArgs"/> instance containing the event data.</param>
  46:          private void SingleInstanceCallback(object sender, InstanceCallbackEventArgs args)
  47:          {
  48:              if (args == null || Dispatcher == null) return;
  49:              Action<bool> d = (bool x) => 
  50:              {
  51:                  var win = MainWindow as MainWindow;
  52:                  if (win == null) return;
  53:   
  54:                  win.ApendArgs(args.CommandLineArgs);
  55:                  win.Activate(x);
  56:              };
  57:              Dispatcher.Invoke(d, true);
  58:          }
  59:      }
  60:  }

[As you can see “App” has “SingleInstanceCallback” that is fired by manager, also you can see function overload for “Window.Actvate” (see this post about this extention)]

VS2010 Sources for this project can be downloaded from here (blog) or here (codeplex).
DLL + PDB Assembly with Library can be downloaded from here (blog) or here (codeplex).
image Project’s site at Codeplex: …

 Remarks: Any comments/improvements will be accepted with pleasure. Of course, you must understand that this code is a contribution and I’m not responsible for any damage that may be caused by using it. All rights reserved ®.

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

25 comments

  1. BladeWise22 בפברואר 2010 ב 20:41

    This solution does not take into account multiple instances on multiple sessions.

    If you try to start a second instance of the same application (I tried the WPF one) on another session, the application crashes on
    private static void RegisterRemoteType(string uri)
    when trying to create the IpcServerChannel.
    It 'thinks' to be the first instance, while it is not, and since the IpcServerChannel is already present, it thows a RemotingException.

    I even think that a bit more code for synchronization is needed to avoid race conditions, and some more try/finally blocks are required to avoid that a ThreadAbortException causes remoting channel server to be not closed.

    Reply
  2. Chris15 במרץ 2010 ב 17:11

    Hi, good article. I liked the pretty diagrams, it looks very professional :-) .
    On an unrelated subject: Did you fix the error?

    Reply
  3. César F. Qüeb6 באפריל 2010 ב 5:00

    Awesome article!…

    Could you share with the developer community the fixed solution (with the second instance on other session), please?…

    Thank you…!, very, very useful…

    Regards

    Reply
  4. saurabh3 במאי 2010 ב 11:15

    I want to open a other instance when i double click on some exe. So how to do it.?

    Reply
  5. glenax27 בינואר 2011 ב 17:46

    A simple and quick solution:
    OnStart {
    Try
    -OpenPort(1234)
    Catch
    -App is running
    -Terminate
    End Try
    }

    OnFirstInstance_Terminate {
    -ClosePort(1234)
    }

    Reply
  6. Hex4 באוגוסט 2011 ב 20:13

    Unfortunately it doesn't work on linux (mono):

    Could not attach to process. If your uid matches the uid of the target
    process, check the setting of /proc/sys/kernel/yama/ptrace_scope, or try
    again as the root user. For more details, see /etc/sysctl.d/10-ptrace.conf
    ptrace: Operation not allowed.

    at (wrapper managed-to-native) System.Threading.WaitHandle.WaitAny_internal (System.Threading.WaitHandle[],int,bool) <0x00004>
    at (wrapper managed-to-native) System.Threading.WaitHandle.WaitAny_internal (System.Threading.WaitHandle[],int,bool) <0x00004>
    at System.Threading.WaitHandle.WaitAny (System.Threading.WaitHandle[],System.TimeSpan,bool) <0x000fe>
    at System.Threading.RegisteredWaitHandle.Wait (object) <0x00094>
    at (wrapper runtime-invoke) object.runtime_invoke_void__this___object (object,intptr,intptr,intptr) <0x00046>

    Any ideas?

    Reply
  7. dklawson23 בנובמבר 2011 ב 22:21

    I think you have a race condition in your code.

    In CreateSingleInstance(), you are testing for the existence of the event handle and then creating it a couple statements later. This is non-atomic. Two different processes could get the same answer to EventWaitHandle.OpenExisting() and think they are both the first instance.

    Instead you should use the EventWaitHandle constructor with the createdNew output parameter:

    eventWaitHandle = EventWaitHandle(false, EventResetMode.AutoReset, eventName, InstanceProxy.IsFirstInstance);

    This will create/retrieve the event handle and tell if you were first or not in a single operation. No try/catch necessary.

    Reply
  8. Brian J1 בדצמבר 2011 ב 23:47

    Awesome. This worked like a charm for me and was exactly what I was looking for. I was going crazy trying to figure out how to pass on the arguments from the second app.

    Thanks!!

    Reply
  9. Hembree1 בינואר 2013 ב 1:26

    This is my first time pay a quick visit at here and
    i am genuinely happy to read all at single place.

    Reply
  10. Saša15 במרץ 2013 ב 10:32

    I have used this pattern in my application and it works generaly, but sometimes I get this exception:
    Unhandled exception!
    System.Runtime.Remoting.RemotingException: Failed to read from an IPC Port: The pipe has been ended.
    Server stack trace:
    at System.Runtime.Remoting.Channels.Ipc.IpcPort.Read(Byte[] data, Int32 offset, Int32 length)
    at System.Runtime.Remoting.Channels.Ipc.Pipe5tream.Read(Byte[] buffer, Int32 offset, Int32 size)
    at System.Runtime.Remoting.Channels.5ocketHandler.ReadFromSocket(Byte[] buffer, Int32 offset, Int32 count)
    at System.Runtime.Remoting.Channels.SocketHandler.Read(Byte[] buffer, Int32 offset, Int32 count)
    at System.Runtime.Remoting.Channels.SocketHandler.ReadAndMatchFourBytes(Byte[] buffer)
    at System.Runtime.Remoting.Channels.Tcp.Tcp5ocketHandler.ReadAndMatchPreamble()
    at System.Runtime.Remoting.Channels.Tcp.Tcp5ocketHandler.ReadVersionAnd0peration(UInt16& operation)
    at System.Runtime.Remoting.Channels.Ipc.IpcClientHandler.ReadHeaders()
    at System.Runtime.Remoting.Channels.Ipc.IpcClientTransport5ink.ProcessMessage(IMessage msg, ITransportHeaders requestHeaders, Stream
    request5tream, ITransportHeaders& responseHeaders, 5tream& response5tream)
    at System.Runtime.Remoting.Channels.BinaryClientFormatter5ink.SyncProcessMessage(IMessage msg)
    Exception rethrown at [U]:
    at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
    at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
    at SinglelnstanceApplication.InstanceProxy.SetCommandLineArgs(Boolean isFirstInstance, String[] commandLineArgs)
    at SinglelnstanceApplication.ApplicationInstanceManager.UpdateRemoteObject(5tring uri)
    at SinglelnstanceApplication.ApplicationInstanceManager.CreateSingleInstance(5tring name, EventHandler‘ 1 callback)

    Can you please help me?

    Reply
  11. Hyatt23 במרץ 2013 ב 17:12

    Thank you for sharing your info. I truly appreciate your efforts and I am waiting for your next write ups
    thank you once again.

    Reply
  12. Hackney24 במרץ 2013 ב 19:37

    If some one needs to be updated with newest technologies then he must be visit this web site and be up to
    date every day.

    Reply
  13. Marcus25 במרץ 2013 ב 22:26

    When someone writes an piece of writing he/she keeps the idea of a user in his/her mind that how a user can understand
    it. So that's why this piece of writing is great. Thanks!

    Reply
  14. Foltz31 במרץ 2013 ב 13:13

    It's very simple to find out any topic on net as compared to books, as I found this article at this website.

    Reply
  15. Lincoln2 באפריל 2013 ב 21:16

    Quality articles is the crucial to attract the users to
    visit the website, that's what this website is providing.

    Reply
  16. Vargas21 באפריל 2013 ב 10:22

    You really make it seem so easy with your presentation but I find
    this matter to be really something which I think I would never understand.

    It seems too complicated and very broad for me.
    I am looking forward for your next post, I'll try to get the hang of it!

    Reply
  17. Dees22 באפריל 2013 ב 2:11

    Greetings from Colorado! I'm bored at work so I decided to browse your site on my iphone during lunch break. I really like the info you present here and can't wait to take a look
    when I get home. I'm surprised at how quick your blog loaded on my phone .. I'm not even
    using WIFI, just 3G .. Anyways, superb site!

    Reply
  18. Laster8 במאי 2013 ב 22:57

    Hello There. I found your blog using msn. This is a really well written
    article. I'll make sure to bookmark it and return to read more of your useful info. Thanks for the post. I will definitely return.

    Reply
  19. Cartwright24 באוגוסט 2013 ב 14:55

    I'd like to find out more? I'd want to find out more details.

    Reply
  20. Domingo17 בספטמבר 2013 ב 11:20

    Attractive portion of content. I just stumbled upon youur
    website annd in accession capitasl too claim that I acquire in fact loved account your blog posts.

    Anyy way I wikl be subscribing to your augment and even I achievement
    you get ntry to constantly rapidly.

    Reply