Preventing multiple instances of an application from running at the same time is a common requirement, especially for business software. By limiting to a single instance of an application per machine, the task of managing network connections, local and remote resources, client sessions and licensing requirements is greatly simplified. An additional advantage is that it allows the application to service requests from other programs. There are many ways of doing this – named pipes, window messages, sockets, temporary files, etc – however this article will focus on one particular method, namely a custom URI scheme.
URI schemes allow programs to issue commands to your application (usually for navigation or performing simple actions), encoded as a URI/URL. By allowing the operating system to handle the URI, neither application has to be aware of the other, nor include any special provisions to communicate with each other. In C#, this is done very easily via the Process.Start()
method.
The following components are required in order to implement this functionality:
- A service class (which is exposed by the application instance via .NET remoting)
- A modified entry point (which will either communicate with the service and then terminate, or start the application normally)
- An installer (to register the URI scheme, which requires elevated privileges)
Service class
The service class acts as both a client and server, depending on how the application was started. It performs the following operations:
- Registers the service as a well-known type and opens an
IpcServerChannel
so that it can be consumed by other instances of the application. - Connects to the service (called from another instance of the application) using an
IpcClientChannel
and returns a remote reference, ornull
if there is only one instance of the application. - Handles the URI by performing the command/action encoded within it.
In order to allow cross-AppDomain
(and therefore cross-process) calls, the service class needs to inherit from MarshalByRefObject
.
public class UriHandler : MarshalByRefObject, IUriHandler { const string IPC_CHANNEL_NAME = "SingleInstanceWithUriScheme"; public static bool Register() { try { IpcServerChannel channel = new IpcServerChannel(IPC_CHANNEL_NAME); ChannelServices.RegisterChannel(channel, true); RemotingConfiguration.RegisterWellKnownServiceType( typeof(UriHandler), "UriHandler", WellKnownObjectMode.SingleCall ); return true; } catch { Console.WriteLine("Couldn't register IPC channel."); Console.WriteLine(); } return false; } public static IUriHandler GetHandler() { try { IpcClientChannel channel = new IpcClientChannel(); ChannelServices.RegisterChannel(channel, true); string address = String.Format("ipc://{0}/UriHandler", IPC_CHANNEL_NAME); IUriHandler handler = (IUriHandler)RemotingServices.Connect(typeof(IUriHandler), address); // need to test whether connection was established TextWriter.Null.WriteLine(handler.ToString()); return handler; } catch { Console.WriteLine("Couldn't get remote UriHandler object."); Console.WriteLine(); } return null; } public bool HandleUri(Uri uri) { // this is only a demonstration; a real implementation would: // - validate the URI // - perform a particular action depending on the URI Program.MainForm.AddUri(uri); return true; } }
Entry point
The application entry point (usually the Main
method in Program.cs
) needs to be modified in order to enforce a single instance of the application and handle URIs supplied via the command-line. The logic is as follows:
- Call
UriHandler.GetHandler()
to connect to the service class. - If a reference is returned and a URI was passed as a command-line argument, call
HandleUri()
on the remote reference. - If a reference is returned but no arguments were passed, exit.
- If a reference is not returned, call
UriHandler.Register()
and display the main form. If, additionally, a URI was passed as a command-line argument, callHandleUri()
on a local instance ofUriHandler
after the form has been shown.
Installer
The URI scheme must be configured in the registry prior to launching the application. Since this usually requires elevated privileges, this step is best performed during the installation of the application.
The code to register the URI scheme is as follows:
const string URI_SCHEME = "myscheme"; const string URI_KEY = "URL:MyScheme Protocol"; const string APP_PATH = @"C:\Code\SingleInstanceWithUriScheme\bin\Debug\Application.exe"; static void RegisterUriScheme() { using (RegistryKey hkcrClass = Registry.ClassesRoot.CreateSubKey(URI_SCHEME)) { hkcrClass.SetValue(null, URI_KEY); hkcrClass.SetValue("URL Protocol", String.Empty, RegistryValueKind.String); using (RegistryKey defaultIcon = hkcrClass.CreateSubKey("DefaultIcon")) { string iconValue = String.Format("\"{0}\",0", APP_PATH); defaultIcon.SetValue(null, iconValue); } using (RegistryKey shell = hkcrClass.CreateSubKey("shell")) { using (RegistryKey open = shell.CreateSubKey("open")) { using (RegistryKey command = open.CreateSubKey("command")) { string cmdValue = String.Format("\"{0}\" \"%1\"", APP_PATH); command.SetValue(null, cmdValue); } } } } }
The APP_PATH
constant above would be replaced with the actual installation path of the application.
Download
SingleInstanceWithUriScheme.zip (Visual Studio 2012 Solution, 82KB)
Usage
- Build the solution.
- Run the installer (
Installer.exe
) to register the URI scheme. - Run the main application executable.
- Use the Windows run command to open a URI such as
myscheme://test
- Observe the URI appearing in the application window.
- Repeat, with and without the application running.
Final words
This is just a basic example of how to create a single instance application and a custom URI scheme, but it can scale to meet the needs of non-trivial Windows Forms applications with similar requirements. If building upon this example, you may wish to consider setting the IPC channel name such that it is unique per user (as opposed to per machine) – this will allow the single instance behaviour to work in a multi-user environment.
This is really a nice guide, thanks for sharing.
For some reason the arguments are not forwarded to my application. Do you have any idea how this is possible? The registration went allright, since it tries to open it with my application. However, no arguments are passed. Thanks in advance.
Does the demo application work for you?
Hello,
Thanks for the article. It’s really wonderful.
I run the sample without issue.
For my own app, some things went wrong.
Forthe first time launching app from UriScheme, it’s working well.
For next opening the app from a protocol.
handler not null, uri not null, it calls the medthod handleUri but don’t execute its implementation.
Do you have any idea for this fix?
If possible, can we Hangouts?
Very nice article, thanks.
However I do not understand why only “Single Instance Applications” can service requests from other applications?
Because it will open a new instance instead of sending to the non-single instance.
Great! saved my job