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:
- It should be able to use the generic WPF template as provided by Visual Studio without any modifications.
- I tshould be able to use any other type of WPF template in general (as long as it isn't too "weird")
- There should not be a need for any additional assemblies beyond the basics for WPF or as few as possible
- The application should have a single instance running at a time
- It should be able to pass startup arguments to the first instance from the second
- If needed or wanted, the first instance should be able to execute special code if a second instance is launched.
- If desired, it should be able to bring the first instance's window to the foreground
- It should follow general coding guidelines
- 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.
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:
- Application starts
- Application checks if other instance is running
- If first instance:
- establish itself as first instance
- setup server communication channel
- run
- wait for second instance communication
- run any special instructions
- If second instance:
- setup up client communication channel
- signal there is a second instance
- send any startup arguments
- 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.
[ServiceContract]
public interface ISingleInstance
{
[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.
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:
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:
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.]
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
.
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:
private bool OpenServiceHost(string uid)
{
try
{
ServiceHost NamedPipeHost = new ServiceHost(typeof(SingleInstance), new Uri("net.pipe://localhost"));
NamedPipeHost.AddServiceEndpoint(typeof(ISingleInstance), new NetNamedPipeBinding(NetNamedPipeSecurityMode.None), uid);
NamedPipeHost.Open();
return true;
}
catch (AddressAlreadyInUseException)
{
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:
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:
public class SingleInstance: ISingleInstance
{
private ServiceHost NamedPipeHost = null;
public void PassStartupArgs(string[] args) { }
private bool OpenServiceHost(string uid)
{
try
{
Application.Current.Exit += new ExitEventHandler(OnAppExit);
NamedPipeHost = new ServiceHost(typeof(SingleInstance), new Uri("net.pipe://localhost"));
NamedPipeHost.AddServiceEndpoint(typeof(ISingleInstance), new NetNamedPipeBinding(), uid);
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(OnUnhandledException);
NamedPipeHost.Open();
return true;
}
catch (AddressAlreadyInUseException)
{
NamedPipeHost.Abort();
NamedPipeHost = null;
return false;
}
catch (CommunicationObjectFaultedException)
{
NamedPipeHost.Abort();
NamedPipeHost = null;
return false;
}
}
private void OnAppExit(object sender, EventArgs e)
{
if (NamedPipeHost != null)
{
NamedPipeHost.Close();
NamedPipeHost = null;
}
}
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.
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.
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
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:
private static void NotifyFirstInstance(string uid)
{
using (ChannelFactory<ISingleInstance> factory = new ChannelFactory<ISingleInstance>(new NetNamedPipeBinding(), new EndpointAddress("net.pipe://localhost/" + uid)))
{
ISingleInstance singleInstanceInterface = factory.CreateChannel();
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:
public class SecondInstanceStartedEventArgs : EventArgs
{
public SecondInstanceStartedEventArgs(string[] args)
{ Args = args; }
public string[] Args { get; set; }
}
Now we add an EventHandler to our SingleInstance
class:
public static event EventHandler<SecondInstanceStartedEventArgs> OnSecondInstanceStarted;
Finally back to PassStartupArgs
we invoke the event:
public void PassStartupArgs(string[] args)
{
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:
public static bool IsFirstInstance(string uid, bool activatewindow)
{
if (OpenServiceHost(uid))
{
if (activatewindow)
OnSecondInstanceStarted += ActivateMainWindow;
return true;
}
NotifyFirstInstance(uid);
Application.Current.Shutdown();
return false;
}
private static void ActivateMainWindow(object sender, SecondInstanceStartedEventArgs e)
{
Application.Current.Dispatcher.Invoke(new Action(() =>
{
if (Application.Current.MainWindow.WindowState == WindowState.Minimized)
Application.Current.MainWindow.WindowState = WindowState.Normal;
Application.Current.MainWindow.Activate();
}));
}
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:
[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:
public partial class App : Application
{
public App()
{
if (SingleInstanceManager.SingleInstance.IsFirstInstance("C69674D6-4A98-417B-ADC0-5919F56AE8FE", true))
{
SingleInstanceManager.SingleInstance.OnSecondInstanceStarted += NewStartupArgs;
SplashScreen splashScreen = new SplashScreen("SplashScreen.jpg");
splashScreen.Show(true);
}
}
private void NewStartupArgs(object sender, SingleInstanceManager.SecondInstanceStartedEventArgs e)
{
}
}
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:
using System;
using System.ServiceModel;
using System.Windows;
namespace SingleInstanceManager
{
[ServiceContract]
public interface ISingleInstance
{
[OperationContract]
void PassStartupArgs(string[] args);
}
public class SecondInstanceStartedEventArgs : EventArgs
{
public SecondInstanceStartedEventArgs(string[] args)
{ Args = args; }
public string[] Args { get; set; }
}
public class SingleInstance : ISingleInstance
{
public static event EventHandler<SecondInstanceStartedEventArgs> OnSecondInstanceStarted;
private static ServiceHost NamedPipeHost = null;
public void PassStartupArgs(string[] args)
{
OnSecondInstanceStarted?.Invoke(this, new SecondInstanceStartedEventArgs(args));
}
public static bool IsFirstInstance(string uid, bool activatewindow)
{
if (OpenServiceHost(uid))
{
if (activatewindow)
OnSecondInstanceStarted += ActivateMainWindow;
return true;
}
NotifyFirstInstance(uid);
Application.Current.Shutdown();
return false;
}
private static bool OpenServiceHost(string uid)
{
try
{
Application.Current.Exit += new ExitEventHandler(OnAppExit);
NamedPipeHost = new ServiceHost(typeof(SingleInstance), new Uri("net.pipe://localhost"));
NamedPipeHost.AddServiceEndpoint(typeof(ISingleInstance), new NetNamedPipeBinding(), uid);
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(OnUnhandledException);
NamedPipeHost.Open();
return true;
}
catch (AddressAlreadyInUseException)
{
NamedPipeHost.Abort();
NamedPipeHost = null;
return false;
}
catch (CommunicationObjectFaultedException)
{
NamedPipeHost.Abort();
NamedPipeHost = null;
return false;
}
}
private static void OnAppExit(object sender, EventArgs e)
{
if (NamedPipeHost != null)
{
NamedPipeHost.Close();
NamedPipeHost = null;
}
}
private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
if (NamedPipeHost != null)
{
if (NamedPipeHost.State == CommunicationState.Faulted)
NamedPipeHost.Abort();
else
NamedPipeHost.Close();
NamedPipeHost = null;
}
}
private static void NotifyFirstInstance(string uid)
{
using (ChannelFactory<ISingleInstance> factory = new ChannelFactory<ISingleInstance>(
new NetNamedPipeBinding(), new EndpointAddress("net.pipe://localhost/" + uid)))
{
ISingleInstance singleInstanceInterface = factory.CreateChannel();
singleInstanceInterface.PassStartupArgs(Environment.GetCommandLineArgs());
}
}
private static void ActivateMainWindow(object sender, SecondInstanceStartedEventArgs e)
{
Application.Current.Dispatcher.Invoke(new Action(() =>
{
if (Application.Current.MainWindow.WindowState == WindowState.Minimized)
Application.Current.MainWindow.WindowState = WindowState.Normal;
Application.Current.MainWindow.Activate();
}));
}
}
}
History
Initial release