single-instance-custom-uri-scheme

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, or null 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:

  1. Call UriHandler.GetHandler() to connect to the service class.
  2. If a reference is returned and a URI was passed as a command-line argument, call HandleUri() on the remote reference.
  3. If a reference is returned but no arguments were passed, exit.
  4. 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, call HandleUri() on a local instance of UriHandler 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

  1. Build the solution.
  2. Run the installer (Installer.exe) to register the URI scheme.
  3. Run the main application executable.
  4. Use the Windows run command to open a URI such as myscheme://test
  5. Observe the URI appearing in the application window.
  6. 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.

7 thoughts on “Single Instance Application with a Custom URI Scheme

  1. vote

    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.

    Reply
  2. vote

    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?

    Reply
  3. vote

    Very nice article, thanks.

    However I do not understand why only “Single Instance Applications” can service requests from other applications?

    Reply

Leave a reply to Bradley Smith Cancel reply

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> 

required