Click here to Skip to main content
14,975,416 members
Articles / Desktop Programming / WPF
Article
Posted 18 May 2017

Stats

12.8K views
200 downloads
14 bookmarked

A Robust and Elegant Single Instance WPF Application Approach Using WCF

Rate me:
Please Sign up or sign in to vote.
5.00/5 (5 votes)
18 May 2017CPOL19 min read
How to utilize WCF to allow a single instance of any WPF application without needing mutexes, extra assemblies, or special "hacks".

Introduction

Today's general design paradigm is a move away from a single running instance that may use the Multiple Document Interface (MDI) approach of the past to allowing multiple instances of the same application with Single Document Interface (SDI). This is seen in products from the like of Microsoft (cf. Office Suite of applications), Adobe, and others. Still there are those applications (like most web browsers) that wish or need to have only a single instance running (whether or not MDI is actually used).

In applications built around WPF if one wants create a single instance of an application there is no magic built-in functionality. One has to code in the checks and validation. There are multiple approaches to solving the problem with each having pros and cons, but all of the solutions I have become familiar with require the application to be coded to fit the solution, instead of providing a solution that is easily adaptable to the general WPF application population.

A good solution should be  able to handle startup arguments, be reusable across multiple projects, should not require any additional assemblies if possible (or at least limit additional reference assemblies to just one), easily convertible into a framework class for distribution,  and on whole allow a programmer to keep to the general WPF project templates. One should not need to code new entry points or make changes to the App.xaml that break the traditional paradigm of it's use. There should be no need to implement overrides on any standard method (i.e. no need to add OnStartup). That is to say, ideally, one should simply need to add a class and make a method call with no additional management.

I searched for a solution to do just this, and while there are lots of approaches none met my needs of having the simple ability to pass command line args if needed, flexibility to do more on a second instance startup, and not require any special handling or coding beyond the basics. After some time and experimentation I hit upon this approach which takes advantage of some special properties of WCF to achieve this objective.

Background

The first instance will always run fine. The essence of the problem is how to deal with any subsequent instance being launched. The obvious solution is for the OS to simply prevent any subsequent launch. Of course, what if I want to launch multiple instances, I wouldn't be able to. This point is moot, as Windows and *nix do not restrict applications in such a way.

This means that we have to perform this programmatically. One approach is to utilize a Mutex.  In this situation the Mutex would be instantiated on the application startup and released on shutdown. Any further instance would try to acquire a lock on the Mutex and being denied would simply know to shutdown.

This approach is very simple and effective. The problem is that if we needed startup parameters from the second instance to be available to the first, this does not provide a mechanism to do that. Also, if there was need for the first instance to perform some action because a second instance was launch, there is no alerting mechanism available either.

Another option (recommended for some time by Microsoft and others) which does allow the ability to pass those command line arguments makes a requirement of using Windows Forms, adding a Visual Basic reference assembly, and creating a new application entry point. This is overly complex and breaks away from a pure WPF paradigm. Additionally, it uses the now deprecated .NET Remoting and one should never rely on deprecated code.

For the solution to be succesful it would appear there needs to be an inter-process communication channel and an event to fire on the second instance launch alerting the first instance. The below approach uses the Windows Communication Framework (WCF), specifically named pipes, and the available Events coding paradigm. A side effect of this approach is there is no need for Mutexes. The approach is also extremely concise and provides a good foundation to build out more complex interactions for specific applications.

Using the code

A quick review of the objectives:

  1. It should be able to use the generic WPF template as provided by Visual Studio without any modifications.
  2. I tshould be able to use any other type of WPF template in general (as long as it isn't too "weird")
  3. There should not be a need for any additional assemblies beyond the basics for WPF or as few as possible
  4. The application should have a single instance running at a time
  5. It should be able to pass startup arguments to the first instance from the second
  6. If needed or wanted, the first instance should be able to execute special code if a second instance is launched.
  7. If desired, it should be able to bring the first instance's window to the foreground
  8. It should follow general coding guidelines
  9. Should run on a wide gamut of .NET versions

The solution is intended to be reusable so we'll create a separate class that can be compiled into a .dll library or added directly to the project. We begin by defining the class. For this we will create a .cs file called SingleInstanceManager with the namespace also being the same.

C#
using System;
using System.ServiceModel;
using System.Windows;

namespace SingleInstanceManager
{
}

We will start the build out  of the class by thinking of it in it's logical process components.  The process flow for achieving a single instance is as follows:

  1. Application starts
  2. Application checks if other instance is running
  3. If first instance:
    1. establish itself as first instance
    2. setup server communication channel
    3. run
    4. wait for second instance communication
      1. run any special instructions
  4. If second instance:
    1. setup up client communication channel
    2. signal there is a second instance
    3. send any startup arguments
    4. shutdown

Now, how to approach the design. The crux of the design is the instance check. The first thing that pops into most developers minds would be to implement an inter-process Mutex. If you can grab a lock on the mutex then you are the only instance running. If you do not get the mutex, you are the secondary instance.

If we didn't need startup arguments or special execution, one could stop with the mutex. We want the more robust approach that allows for the passing of startup arguments across an inter-process communication channel. Before we get to involved with the instance check, let's examine the nature of this communication channel so we can understand how after the check we need to proceed with the channel setup.

In the pastfor inter-process communication there was .NET Remoting, but that has  been deprecated for some time in favour of the Windows Communication Foundation (WCF). WCF provides multiple avenues and methods for creating the inter-process communication. We'll proceed on the assumption that the application will be running on the same local desktop machine. In addition we don't need fancy mechanics because we are simply sending agross a string of arguments. The WCF transport Named Pipes is designed for exactly this type of scenario and in the end we'll get something looking very much like a remote procedure call. [Note: this example could be extended to using other transports so that you could design a way to restrict the single instance to not just to the desktop but across a network as well.]

What the code will do is, in essence, to expose a method to the outside world, or in a more literal sense to all processes on the Windows desktop. WCF can then create a tunnel from another process, referred to as the client (in this scenario it is the second instance of our application), to the process of the first instance, which  is referred to as the server, and in particular to that class method of the first instance. We'll establish this communication with the following steps.

First, define an Interface. We use the attribute [ServiceContract] to indicate that this interface is to be exposed to clients. The attribute [OperationContract] instructs that this method is available for use by the clients. The  second instance will call the service method PassStartupArgs and the variable args will contain the command-line, or startup, arguments for the second instance.

C#
///<summary> The WCF interface for passing the startup parameters </summary>
[ServiceContract]
public interface ISingleInstance
{
     /// <summary> Notifies the first instance that another instance of the application attempted to start. </summary>
     /// <param name="args">The other instance's command-line arguments.</param>
     [OperationContract<]
     void PassStartupArgs(string[] args);
}

Now to create the class that will implement our service and manage the single instance approach.

First we define our class SingleInstance and indicate it will use the ISingleInstance interface. Next we define the interface's method PassStartupArgs. Once the WCF channel is open, it is this method that can be interacted with. Let's continue to focus on the WCF plumbing, so for now we'll leave the PassStartupArgs method empty.

C#
/// <summary>
/// A class to use for single-instance applications.
/// </summary>
public class SingleInstance : ISingleInstance
{
     public void PassStartupArgs(string[] args)
     {
     }
}

The purpose of this next method will be to instantiate or create the WCF server. We'll call this method OpenServiceHost. It does not need to be accessible outside of the class so it can be private. We will want a return indicator of whether the WCF server was able to be started and is listening, so we'll use a return type of bool. Return true if everything succeeded or false if there was a problem. At the moment our method looks like this:

C#
/// <summary>
/// Attempts to create the named pipe service.
/// </summary> 
/// <returns>true, if the service was published successfully.</returns>
private bool OpenServiceHost()
{
}

Our channel is made available through the ServiceHost class. The ServiceHost object is what will be listening for the incoming communication from the second instance. To initizalise our ServiceHost object we need to provide it's constructor with the service type being used (in this case it is our SingleInstance class type) and the URI of our service address. The URI for a localhost named pipe is simply net.pipe:://localhost.

Note Bene: WCF provides three main transports: HTTP, TCP, and named pipes. HTTP is targeted for web based and non-WCF legacy communication. TCP is targeted for communication between different machines. According to Microsoft: "A named pipe is an object in the Windows operating system kernel, such as a section of shared memory that processes can use for communication. A named pipe has a name, and can be used for one-way or duplex communication between processes on a single machine." Named pipes are targeted for communication between applications on a single machine. As stated above, we will be using named pipes in our service.

Our initialization is thus:

C#
ServiceHost NamedPipeHost = new ServiceHost(typeof(SingleInstance), new Uri("net.pipe://localhost"));

With NamedPipeHost initialized we need to create the service end points. The end points represent the "tunnel" for the communication. The end points enable the particular transport binding for the service and give the address for the client to send communications to. This is done with the AddServiceEndpoint method of ServiceHost class. We need to supply the service contract (here our interface), the type of binding (in this case NetNamedPipeBinding and an address which will be appended to our base URI of "net.pipe://localhost", which for now we'll just use the string "startupargs". [NB, the NetNamedPipe binding provides a transport security level. Depending on how one feels about the overhead and the need for security on this particular message of startup arguments you could pass NetNamedPipeSecurityMode.Node as a field in the NetNamedPipeBinding constructor to disable the transport security which is on by default.]

C#
NamedPipeHost.AddServiceEndpoint(typeof(ISingleInstance), new NetNamedPipeBinding(), "startupargs");

After the endpoint has been added we just need to open the channel and start the service listening by making a call to the Open method on NetNamedPipeBinding.

C#
NamedPipeHost.Open();

That is everything for our named pipe server, it is at this point of code execution ready to allow the method PassStartupArgs to be called from another process. Before proceeding, let us quickly review some fault checking on our server, which checking will, coincidentaly, have a large impact on our design.

You may only have one service listening on a particular address, otherwise the exception System.ServiceModel.AddressAlreadyInUseException is thrown when the Open method is invoked. Currently we have "net.pipe://localhost/startupargs" as the address. If this code was reused in a different application an exception would be thrown upon launch of that application. We'll want a unique address for each application that uses this code.

By creating a unique address that the application uses on every startup we can utilize the AddressAlreadyInUseException as an alternative to a mutex for our instance checking. We no longer have to manage a mutex and we can simplify the design and the code complexity.

To accomplish all of this, we modify our method to accept a string, that the application will pass along (which interface we will design shortly), representing our unique address (think of it as our unique id for the application), and wrap the named pipe creation in a try/catch block. If we can open the service (and thus are the first instance) we return true for success, or if we catch the exception (and are thus a secondary instance) we return false.

Our code now looks like this:

C#
/// <summary>
/// Attempts to create the named pipe service.
/// </summary>
/// <param name="uid">the application's unique identifier used to create a unique endpoint address.</param>
/// <returns>true, if the service was published successfully.</returns>
private bool OpenServiceHost(string uid)
{
    try
    {
         //setup the WCF service using NamedPipe
         ServiceHost NamedPipeHost = new ServiceHost(typeof(SingleInstance), new Uri("net.pipe://localhost"));
         NamedPipeHost.AddServiceEndpoint(typeof(ISingleInstance), new NetNamedPipeBinding(NetNamedPipeSecurityMode.None), uid);

         //if the service is already open (i.e. another instance is already running) the following will cause an exception
         NamedPipeHost.Open();
         return true; //success
    }
    catch (AddressAlreadyInUseException)
    {
        //failed to open the service so must be a second instance
        return false;
    }
}

Continuing with our fault check review, it probably occurs to one that there is no provision to close and cleanup our ServiceHost object. There's a particular aspect of this object that preculeds this need. The object will be constructed on the application thread, and because we are using WPF, the UI thread will keep the application alive, and thus the ServiceHost object as well, for the duration of the application, unless it is specifically closed. It will be disposed of and become available for garbage collection when the application shuts down. That is we do not need to do any "cleanup" of NamedPipeHost; it will manage itself, all we need to do is open the channel. There is no need to track or worry about our ServiceHost object, and this makes it very ideal to be used in this "add-on" type of class.

With that being said, there is a very slight chance for a race condition. As the application shuts down, messages may continue to flow in before the channel is disposed, and, of course, those messages will not be processed hanging the client connection for a brief time. There's a slight risk our second instance could end up in some unknown state. If we desire to mitigate the risk completely we can explicity close the host on shutdown of the application.

To perform this, we hook the Exit event and add a new event handler. We will also need to make NamedPipeHost a class variable so it can be addressed from the scope of the event method.  We add the following to our OpenServiceHost method:

Application.Current.Exit += new ExitEventHandler(OnAppExit);

And we create our OnAppExit method:

C#
private static void OnAppExit(object sender, EventArgs e)
{
    if (NamedPipeHost != null)
    {
        NamedPipeHost.Close();
        NamedPipeHost = null;
    }
}

There are two other faults to be cognizant of. One is if our listener enters a "fault" state or there is an unhandled exception and we do not have a graceful shutdown of the application. Either of these can lead to a situation where the service is in an unknown state and any future launch is unable to move forward because it is unable to open the service channel.

If the service enters a Faulted state it simply won't be able to process any new client connections. However, when the secondary instance tries to open the service it will get a CommunicationObjectFaultedException instead. I feel it is better coding practice to catch on just the exceptions we plan to handle instead using the catchall Exception, allowing a developer to decide how he wants to deal with other types of exceptions. In this case we add another catch block and dispose of the service.

Because NamedPipeHost is also a class variable we should dispose of it properly in our catch statements. For these particular exceptions NamedPipeHost will be in a "faulted" state and if we call the Close method an exception will be thrown. Instead we call Abort and then set the variable to null.

For an undhandled exception we want to ensure that NamedPipeHost is properly disposed of. So we hook AppDomain.CurrentDomain.UnhandledException to trap the unhandled exceptions and dispose of the host (checking first if it is in a faulted state).

We now have code like this:

C#
public class SingleInstance: ISingleInstance
{
    private ServiceHost NamedPipeHost = null;

    public void PassStartupArgs(string[] args) { }

    /// <summary>
    /// Attempts to create the named pipe service.
    /// </summary>
    /// <param name="uid">the application's unique identifier used to create a unique endpoint address.</param>
    /// <returns>true, if the service was published successfully.</returns>
    private bool OpenServiceHost(string uid)
    {     
        try
        {
            //hook the application exit event to avoid a race condition when messages flow while the application is disposing of the channel during shutdown
            Application.Current.Exit += new ExitEventHandler(OnAppExit);

            //setup the WCF service using NamedPipe
            NamedPipeHost = new ServiceHost(typeof(SingleInstance), new Uri("net.pipe://localhost"));
            NamedPipeHost.AddServiceEndpoint(typeof(ISingleInstance), new NetNamedPipeBinding(), uid);

            //for any unhandled exception we need to ensure NamedPipeHost is disposed
             AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(OnUnhandledException);
            
            //if the service is already open (i.e. another instance is already running) the following will cause an exception
            NamedPipeHost.Open();
            
            //success and we are first instance
            return true;
        }
        catch (AddressAlreadyInUseException)
        {
            //failed to open the service so must be a second instance
            NamedPipeHost.Abort();
            NamedPipeHost = null;
            return false;
        }
        catch (CommunicationObjectFaultedException)
        {
            //failed to open the service so must be a second instance
            NamedPipeHost.Abort();
            NamedPipeHost = null;
            return false;
        }
    }

    /// <summary>
    /// Ensures that the named pipe service host is closed on the application exit
    /// </summary>
    private void OnAppExit(object sender, EventArgs e)
    {
        if (NamedPipeHost != null)
        {
            NamedPipeHost.Close();
            NamedPipeHost = null;
        }
    }

    /// <summary>
    /// ensure host is disposed of if there is an unhandled exception
    /// </summary>
    private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        if (NamedPipeHost != null)
        {
            if (NamedPipeHost.State == CommunicationState.Faulted)
                NamedPipeHost.Abort();
            else
                NamedPipeHost.Close();
            NamedPipeHost = null;
        }
     }
}

This code completes our WCF service instanstiation. It also provides a way to provide the answer to which instance which we can now take advantage of and implement our check method.

Following our process flow after the application launches it will need to perform the instance check. Our method, will try to open the WCF service. If it succeeds it can tell the application it is the first instance and proceed. If it fails we are a secondary instance and need to proceed to notify the first instance. This is a simple implementation and there is no need to make the developer instantiate an instance of our SingleInstanceManager class. Following this design principle we will make this method static, which necessitates that we update our other methods to static where needed (at this moment that is every method except PassStartupArgs).

We'll designate the method as IsFirstInstance. It will need a return type of bool to indicate to the application whether it is a first or secondary instance (true if first instance, otherwise false). Remembering our service host needs a unique id we will have a parameter string uid for the application to pass in a unique id for the named pipe address.

C#
public static bool IsFirstInstance(string uid) { }

To check if we are the first instance or not our method needs to call OpenServiceHost and test the return code. If OpenServiceHost returns true then this is the first instance and this method can return true. If it returns false we need to notify the first instance, pass the startup arguments, and then proceed to shutdown this secondary instance.

To accomplish this we need another method that will establish the WCF client and pass the startup arguments using our interface PassStartupArgs. This method can be private and has no need of a return type. It will need the unique id so the client can connect on the correct address.

C#
private static void NotifyFirstInstance(string uid) { }

To establish the connection between the client and server a channel, or tunnel, needs to be setup. WCF provides ChannelFactory<TChannel> to do this. We need to supply our ISingleInstance and the constructor will take a binding and our endpoint address. We can wrap this factory in a using statement

C#
using (ChannelFactory<ISingleInstance> factory = new ChannelFactory<ISingleInstance>(new NetNamedPipeBinding(), new EndpointAddress("net.pipe://localhost/" + uid))) { }

We can now call CreateChannel on our factory which will return the interface connected to our first instance. We merely call PassStartupArgs on the returned interface and we are communicating with the first instance. The complete method looks like this:

C#
private static void NotifyFirstInstance(string uid)
{
    //create channel with first instance interface
    using (ChannelFactory<ISingleInstance> factory = new ChannelFactory<ISingleInstance>(new NetNamedPipeBinding(), new EndpointAddress("net.pipe://localhost/" + uid)))
    {
        ISingleInstance singleInstanceInterface = factory.CreateChannel();

        //pass the startup args to the first instance
        singleInstanceInteface.PassStartupArgs(Environment.GetCommandLineArgs());
    }
}

Now that we are communicating with our interface let's complete that method. Our interface needs to do something with the startup arguments or when the second instance is launched. One could just insert the needed code right here. If you reuse the code you just need to know to fill out this method with whatever is needed. Another approach is to attach the interface to the WPF App class and shift the code to reference App.PassStartupArgs. That is more difficult and breaks paradigm of having a separate class handle all of this. The approach we'll look at here is registering an event that will be raised within PassStartupArgs. This gives added flexibility because the reaction to a second instance can be changed with time.

To implement the event we need to create a separate class within our SingleInstanceManager namespace. The event only needs to deal with the startup arguments which are string[] args. Here is the event:

C#
/// <summary>
/// Event declaration for the startup of a second instance
/// </summary>
public class SecondInstanceStartedEventArgs : EventArgs
{
    /// <summary>
    /// The event method declartion for the startup of a second instance
    /// </summary>
    /// <param name="args">The other instance's command-line arguments.</param>
    public SecondInstanceStartedEventArgs(string[] args)
    { Args = args; }

    /// <summary>
    /// Property containing the second instance's command-line arguments
    /// </summary>
    public string[] Args { get; set; }
}

Now we add an EventHandler to our SingleInstance class:

C#
/// <summary>
/// Is raised when another instance attempts to start up.
/// </summary>
public static event EventHandler<SecondInstanceStartedEventArgs> OnSecondInstanceStarted;

Finally back to PassStartupArgs we invoke the event:

C#
/// <summary>
/// Interface: Notifies the first instance tht another instance of the application attempted to start.
/// </summary>
/// <param name="args">The other instance's command-line arguments.</param>
public void PassStartupArgs(string[] args)
{
    //check if an event is registered for when a second instance is started and if so raise the event
    OnSecondInstanceStarted?.Invoke(this, new SecondInstanceStartedEventArgs(args));
}

The IsFirstInstance method can now call NotifyFirstInstance and everything should work.

A final consideration before looking at how to implement this solution in the application. It is possible to provide in the class a way to bring the first instance window to the foreground. This may not always be desired, so we can add a flag to IsFirstInstance parameter list to alert for the need to bring the first instance window forward. If the flag is true we can accomplish this by hooking the event we just implemented. The code would look like this:

C#
/// <summary>
/// Checks to see if this instance is the first instance of this application on the local machine.  If it is not, this method will
/// send the first instance this instance's command-line arguments.
/// </summary>
/// <param name="uid">The application's unique identifier.</param>
/// <param name="activatewindow">Should the main window become active on a second instance launch</param>
/// <returns>True if this instance is the first instance.</returns>
public static bool IsFirstInstance(string uid, bool activatewindow)
{
    //attempt to open the service, should succeed if this is the first instance
    if (OpenServiceHost(uid))
    {
        if (activatewindow)
            OnSecondInstanceStarted += ActivateMainWindow;
        return true;
    }
    //notify the first instance and pass the command-line args
    NotifyFirstInstance(uid);

    //ok to shutdown second instance
    Application.Current.Shutdown();
    return false;            
}

/// <summary>
/// Activate the first instance's main window
/// </summary>
private static void ActivateMainWindow(object sender, SecondInstanceStartedEventArgs e)
{
    //activate first instance window
    Application.Current.Dispatcher.Invoke(new Action(() =>
        {
            //check if window state is minimized then restore
            if (Application.Current.MainWindow.WindowState == WindowState.Minimized)
                Application.Current.MainWindow.WindowState = WindowState.Normal; //despite saying Normal this actually will restore
            Application.Current.MainWindow.Activate(); //now activate window
        }));
}

A quick overview of  ActivateMainWindow. Because we are interacting with the UI  we must use the Dispatcher and marshall our commands to the UI thread. We want to bring the application window forward but the window could be minimized. If so we want to return it to the same state it held when it was minimized. The proper approach is to check the window state and if it is Minimized set the state to Normal which will return it to the previous state (not a default or restored state). Then we invoke the Activate method.

That completes the SingleInstanceManager. Now to implement it all from the application. The secondary instance needs to perform its instance check right after launch and before the WPF UI starts. This means it can't be inserted into the MainWindow.xaml.cs. However, WPF is different from other aspects of .NET in that it hides the Main() method. This is auto-generated and appears in App.g.cs. This generated code looks similar to this:

C#
/// <summary>
/// Application Entry Point.
/// </summary>
[System.STAThreadAttribute()]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
public static void Main() {
    AnApplication.App app = new AnApplication.App();
    app.InitializeComponent();
    app.Run();

One can override this Main() but the goal is to work with the typical WPF template. So, with this in mind, looking over this generated code, we can see the earliest we can get control to perform the check is in the App constructor. This constructor is found in the App.xaml.cs file. We add the constructor public App() and call our method IsFirstInstance. We pass in a unique string (the example below uses a generated GUID) and true or false depending on whether we want the first instance window to come to the fore on a second instance launch (the example uses true).

Since we want to deal with startup arguments, we register an event handler if IsFirstInstance returns true. We can add any additional code we want for the constructor here. This is a good place to insert code to show a splash screen. The splash screen will execute on a different thread from the main application, and the secondary instance shutdown could cause a problem because of this. In addition, one probably doesn't want the splash screen to show on a second instance launch.

The only break from our objectives is that we must add the System.ServiceModel (which handles WCF) assembly to our references for the project. This should be acceptable in most situations.

Our application code will look similar to the following:

C#
public partial class App : Application 
{
    public App()
    {
        //check if we are the first instance
        if (SingleInstanceManager.SingleInstance.IsFirstInstance("C69674D6-4A98-417B-ADC0-5919F56AE8FE", true))
        {
            //we are, register our event handler for receiving the new arguments
            SingleInstanceManager.SingleInstance.OnSecondInstanceStarted += NewStartupArgs;
            
            //place additional startup code here
            SplashScreen splashScreen = new SplashScreen("SplashScreen.jpg");
            splashScreen.Show(true);
        }
        //we are secondary instance and shutdown will happen automatically
    }

    private void NewStartupArgs(object sender, SingleInstanceManager.SecondInstanceStartedEventArgs e)
    {
       //handle new startup arguments and/or do anything else for second instance launch
    }
}

You now have a working application that will "launch" only a single instance, process new startup arguments, and bring the first instance window to the fore (if desired). We accomplish all of this in roughly less than 200 lines of code with a single method call to implement. Much more can be done to customize or enhance this solution, but it's a great solution for a general implementation across multiple projects.

The full code for the SingleInstanceManager namespace follows:

C#
using System;
using System.ServiceModel;
using System.Windows;

namespace SingleInstanceManager
{
    /// <summary> The WCF interface for passing the startup parameters </summary>
    [ServiceContract]
    public interface ISingleInstance
    {
        /// <summary>
        /// Notifies the first instance that another instance of the application attempted to start.
        /// </summary>
        /// <param name="args">The other instance's command-line arguments.</param>
        [OperationContract]
        void PassStartupArgs(string[] args);
    }

    /// <summary>
    /// Event declaration for the startup of a second instance
    /// </summary>
    public class SecondInstanceStartedEventArgs : EventArgs
    {
        /// <summary>
        /// The event method declaration for the startup of a second instance
        /// </summary>
        /// <param name="args">The other instance's command-line arguments.</param>
        public SecondInstanceStartedEventArgs(string[] args)
        { Args = args; }

        /// <summary>
        /// Property containing the second instance's command-line arguments
        /// </summary>
        public string[] Args { get; set; }
    }

    /// <summary>
    /// A class to allow for a single-instance of an application.
    /// </summary>
    public class SingleInstance : ISingleInstance
    {
        /// <summary>
        /// Is raised when another instance attempts to start up.
        /// </summary>
        public static event EventHandler<SecondInstanceStartedEventArgs> OnSecondInstanceStarted;

        private static ServiceHost NamedPipeHost = null;

        /// <summary>
        /// Interface: Notifies the first instance that another instance of the application attempted to start.
        /// </summary>
        /// <param name="args">The other instance's command-line arguments.</param>
        public void PassStartupArgs(string[] args)
        {
            //check if an event is registered for when a second instance is started
            OnSecondInstanceStarted?.Invoke(this, new SecondInstanceStartedEventArgs(args));
        }

        /// <summary>
        /// Checks to see if this instance is the first instance of this application on the local machine.  If it is not, this method will
        /// send the first instance this instance's command-line arguments.
        /// </summary>
        /// <param name="uid">The application's unique identifier.</param>
        /// <param name="activatewindow">Should the main window become active on a second instance launch</param>
        /// <returns>True if this instance is the first instance.</returns>
        public static bool IsFirstInstance(string uid, bool activatewindow)
        {
            //attempt to open the service, should succeed if this is the first instance
            if (OpenServiceHost(uid))
            {
                if (activatewindow)
                    OnSecondInstanceStarted += ActivateMainWindow;
                return true;
            }
            //notify the first instance and pass the command-line args
            NotifyFirstInstance(uid);

            //ok to shutdown second instance
            Application.Current.Shutdown();
            return false;            
        }

        /// <summary>
        /// Attempts to create the named pipe service.
        /// </summary>
        /// <param name="uid">The application's unique identifier.</param>
        /// <returns>True if the service was published successfully.</returns>
        private static bool OpenServiceHost(string uid)
        {            
            try
            {
                //hook the application exit event to avoid race condition when messages flow while the application is disposing of the channel during shutdown
                Application.Current.Exit += new ExitEventHandler(OnAppExit);

                //setup the WCF service using a NamedPipe
                NamedPipeHost = new ServiceHost(typeof(SingleInstance), new Uri("net.pipe://localhost"));
                NamedPipeHost.AddServiceEndpoint(typeof(ISingleInstance), new NetNamedPipeBinding(), uid);

                //for any unhandled exception we need to ensure NamedPipeHost is disposed
                AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(OnUnhandledException); 

                //if the service is already open (i.e. another instance is already running) this will cause an exception
                NamedPipeHost.Open();
                
                //success and we are first instance
                return true;
            }
            catch (AddressAlreadyInUseException)
            {
                //failed to open the service so must be a second instance
                NamedPipeHost.Abort();
                NamedPipeHost = null;
                return false;
            }
            catch (CommunicationObjectFaultedException)
            {
                //failed to open the service so must be a second instance
                NamedPipeHost.Abort();
                NamedPipeHost = null;
                return false;
            }
        }

        /// <summary>
        /// Ensures that the named pipe service host is closed on the application exit
        /// </summary>
        private static void OnAppExit(object sender, EventArgs e)
        {
            if (NamedPipeHost != null)
            {
                NamedPipeHost.Close();
                NamedPipeHost = null;
            }
        }

        /// <summary>
        /// ensure host is disposed of if there is an unhandled exception
        /// </summary>
        private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            if (NamedPipeHost != null)
            {
                if (NamedPipeHost.State == CommunicationState.Faulted)
                    NamedPipeHost.Abort();
                else
                    NamedPipeHost.Close();
                NamedPipeHost = null;
            }
        }

        /// <summary>
        /// Notifies the main instance that this instance is attempting to start up.
        /// </summary>
        /// <param name="uid">The application's unique identifier.</param>
        private static void NotifyFirstInstance(string uid)
        {
            //create channel with first instance interface
            using (ChannelFactory<ISingleInstance> factory = new ChannelFactory<ISingleInstance>(
                new NetNamedPipeBinding(), new EndpointAddress("net.pipe://localhost/" + uid)))
            {
                ISingleInstance singleInstanceInterface = factory.CreateChannel();
                //pass the command-line args to the first interface
                singleInstanceInterface.PassStartupArgs(Environment.GetCommandLineArgs());
            }
        }

        /// <summary>
        /// Activate the first instance's main window
        /// </summary>
        private static void ActivateMainWindow(object sender, SecondInstanceStartedEventArgs e)
        {
            //activate first instance window
            Application.Current.Dispatcher.Invoke(new Action(() =>
                {
                    //check if window state is minimized then restore
                    if (Application.Current.MainWindow.WindowState == WindowState.Minimized)
                        Application.Current.MainWindow.WindowState = WindowState.Normal; //despite saying Normal this actually will restore
                    Application.Current.MainWindow.Activate(); //now activate window
                }));
        }
    }
}

History

Initial release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Jon Campbell
Architect
United States United States
I am a Solution Architect for IU Health architecting software solutions for the enterprise. Prior I had been employed with eBay as a Project Manager and Lead (Enterprise Architect) which focused on managing the scope, timeline, resource, and business expectations of third party merchants and their first-time onboarding to the eBay marketplace. I also acted as an Enterprise Architect leading the merchant in the design, reliability, and scaling of their infrastructure (both hardware and software). Prior I worked for Adaptive Computing as a Sales Engineer. I was responsible for working with customers and helping with the sales life cycle and providing input to Program Management. Prior I was employed as a High Performance Computing Analyst. I was responsible for administering and maintaining a high performance / distributed computing environment that is used by the bank for financial software and analysis. Previous, I had been employed at ARINC, where as a Principal Engineer I worked in the Systems Integration and Test group. The division I worked in represented the service end of the airline communication network. Prior to this position I worked for the government in several contractual roles which allowed me to lead a small team in consulting and evaluating research initiatives and their funding related to discovering and negating threats from weapons of mass destruction. Other contracts included helping the Navy in a Signals Intelligence role as the lead hardware and systems architect/engineer; a Senior Operations Research Analyst assessing force realignment and restructuring for the joint Explosive Ordnance Disposal community with a project assessing the joint force structure of the EOD elements, both manning, infrastructure, and overall support and command chain structures. I also spent three years involved with the issue of Improvised Explosive Devices and for the Naval EOD Technology Division where I acted as the lead engineer for exploitation.

Comments and Discussions

 
QuestionDebug from studio vs. cmdline instance Pin
JaPonCz6-Apr-20 3:10
MemberJaPonCz6-Apr-20 3:10 
QuestionI keep getting "There was no endpoint listening at net.pipe://localhost" Pin
Member 118305931-Nov-17 3:41
MemberMember 118305931-Nov-17 3:41 
AnswerRe: I keep getting "There was no endpoint listening at net.pipe://localhost" Pin
Member 118305931-Nov-17 7:44
MemberMember 118305931-Nov-17 7:44 
QuestionMutex versus Exception Pin
ISanti30-May-17 0:23
MemberISanti30-May-17 0:23 
AnswerRe: Mutex versus Exception Pin
Jon Campbell30-May-17 4:14
professionalJon Campbell30-May-17 4:14 
GeneralRe: Mutex versus Exception Pin
ISanti30-May-17 22:16
MemberISanti30-May-17 22:16 
GeneralMy vote of 5 Pin
Joachim Blank21-May-17 20:29
MemberJoachim Blank21-May-17 20:29 
GeneralMy vote of 5 Pin
Erik Rude19-May-17 1:50
MemberErik Rude19-May-17 1:50 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.