Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

All Three Features of Single-Instance Applications at One Shot, .NET

4.94/5 (25 votes)
16 Feb 2017CPOL15 min read 49.9K   409  
Single-instance application behavior can be considered comprehensive only it all three features are implemented: detection of second instance, passing command line and activation of first instance

Image 1

Contents:

  1. Introduction
  2. Main Idea
  3. The Usage
  4. Implementation
  5. Unique IPC Names
  6. Single-Instance Facility and Test/Demo Applications
  7. Build and Platform Compatibility
  8. Afterword

1. Introduction

Long time ago, I found that the application single-instance behavior is not what is provided by the OS API, so it needs development of a special facility, which present certain pretty serious problems related to IPC and atom uniqueness. Later on, I realize that my own implementation, working in most practical situations, is still far from satisfactory. Step by step, I finally develop some approach which I consider comprehensive. My recent search for analogous solution still did not give me anything which would satisfy me, so I hope my solution will be really helpful for many developers.

The problem is that a typical solution is concentrated on only one aspect: finding out that the application instance is the second one, so it should be terminated. Considering this task as a main one leads to inadequate technique for communication between instances. In one approach, all processes in the system are listed, and the current instance is compared with the list of processes. In another, sadly, very popular approach, shared memory is used. The problems are related to wrong posing of the problem. If the first feature, recognition of the first instance, is considered as the main one, all thinking goes in wrong direction. Right idea would be: "main" does not mean defining. Well-known application with the single instance-behavior actually manifest three important features:

  1. When an instance is started, it finds out if previously started instance is already running. In this cases, the second instance terminates itself.
  2. Optionally, but typically, before termination, the second instance transmit its command line to the first instance. In this case, the second instance receives this information and handles it, usually loads files listed in the command line.
  3. Optionally, and also typically, the first instance handle the start of the first instance by showing itself on top of the other application windows on the desktop and focusing appropriate element of its UI.

Right approach would be considering all three requirements in a holistic manner, as equally important and typical. It should inevitably lead to right conclusion on the IPC technique: it should be chosen based on the most data-intensive requirement, passing command line data, other two requirements implemented as the side effects of such message-oriented communication.

So, I invite my readers not just to get familiar with my approach, but also to follow my logic.

2. Main Idea

It's actually pretty easy to explain the idea in few words. Let's do some logical inferences. First of all, the OS are designed to have all instances of all application equal independent different processes. And all processes in modern OS are highly isolated, they operate in separate address spaces, so the numerical value of some object's address in one application may be something completely different in the different process, even if this process is another instance of the same application.

This means, to introduce some communication between these instances, different processes, we need to use some IPC. There are different kinds of IPC, including those routinely used for implementation of single-instance behavior, such as shared memory, but also there are named mutexes, event wait handles, and so on. Which one to use? Let's see which of our three features is the most data-intensive? Of course, this is passing a command line. And the character of the interaction is message-oriented: a second instance sends its command line to the first instance where it should be handled. Hence, we need some IPC of messaging kind.

The remaining steps are nearly predefined. Having a separate facility to detect a first instance-process seems redundant. All we have to is to try sending a message. The failure will mean the absence of the first instance. In this case, the current instance should be considered the first one; and the message-accepting mechanism should be created. Another instance will successfully connect to the first line and send some message (or messages) to the first instance; then it should quite.

Basically, that's all. But now, let's pick the implementation of the messaging IPC facility. When I looked at the problem from this side, I immediately picked "classical" .NET remoting. It requires minimal effort and is quite sufficient and reliable. At the same time, WCF looks like a bit of overkill which could not bring anything useful compared to remoting, besides, it would support less .NET versions. Now, the channels. It's apparent that the processes required to communicate are running on the same machine. So, they can communicate via the socket API (it was the first candidate for messaging implementation if remoting did not exist), or named pipes, which is a cross-platform feature. Well, in remoting, there is a predefined channel based on pipes, and it is also called "IPC", which I would dub "IPC in narrower sense of this word". This is the perfect choice.

At the same time, the mechanism can be easily upgraded to some distributed mechanism, where the role of the application can be played by some process running on any of the network hosts of some set of network nodes, without having and predefined location of this application. But this is a whole different story.

For now, let's limit our discussion to the single machine. Here is the summary of the picture:

We develop remoting IPC functionality where the first instance sets up a remote object and acts as a server, and the second instance tries to act as a client, connects to the remote server's object and send some messages to it. The first instance, server part, handles the messages.

Some application performs the check for the other process instance. It tries to connect to some remoting server, using some unique naming schema. It will register the channel and try to connect to the server's remote object by using remoting activation and send some message to it, actually, call a remote method. If this is the only instance in the system, it will fail, because the remote object was never created. In this case, the current instance is the first one, it should silently handle the exception and create a remote object and register it using Remoting Services. This way, it will be ready for handling messages from a second instance.

If connection to the remote object is successful, it means that the current application instance is not the first one. The it can call the remote object interface members. Optionally, it can send its command line, request the first instance to activate, or both. The first instance, through these remote calls, should handle all of some of the requests in some application-specific way.

That's all. Since this moment, you don't have to read any further and can simply download and review the source code supplied with this article (see the top of the article). For those who prefer to see the explanation of the usage and key implementation detail, I'll continue.

3. The Usage

First of all, the single-instance facility should not depends on application type. All three features should be optional; they should not depend on the application type. Moreover, even if passing of the command line and activation of the first instance are used, the handling of these request should be customizable.

Here is how it may look in a general case. Main play takes place in the application entry point method (Main):

C#
static void Main(string[] commandLine) {
    if (SingleInstanceManager.IsSecondInstance) {
        SingleInstanceManager.HandleRemoteCommandLine(commandLine);
        SingleInstanceManager.ActivateFirstInstance();
        return;
    } //if
    // the usual Main method stuff here:
    // ...
}

This fragment of code clearly demonstrates all three features of the optional single-instance behavior. First feature is the combination of "if" and "return". Naturally, it's optional: if this block is missing, the application will run any number of instances. Two remaining features, HandleRemoteCommandLine and ActivateFirstInstance are also optional.

However, this is not all. When the SingleInstanceManager.IsSecondInstance returns false, first instance will prepare and register its remote object to handle requests from the second instance later. This is already done in the implementation of this method. But handling of these two request needs to be customized. By default, the implementations of handling are always implemented and do nothing. To do something, the application code needs to subsribe to one or two static events invoked when the second instance received a message.

This is the skeleton of the implementation of such handling:

C#
SingleInstanceManager.FilesLoading += (sender, eventArgs) => {
   Dispatcher.Invoke(new System.Action(() => DoSomethingWithCommandLine(eventArgs.CommandLine) ));
};
// and
SingleInstanceManager.FirstInstanceShowing += (sender, eventArgs) => {
   Dispatcher.Invoke(new System.Action(() => DoSomethingToActivate() ));
};

Note the use of Dispatcher.Invoke. This is the mechanism of delegation of some processing to from any thread to the application UI thread. It is introduced in WPF but can also used in System.Windows.Forms, where, alternatively, System.Windows.Forms.Control.Invoke can be used. It is critically important, because the events will be invoked in some remoting thread, not a UI thread. As our implementation of the SingleInstanceManager facility is supposed to be universal, independent of the application type or particular UI framework/library used; it can be a console-only application, or anything else.

I think the usage should be clear by now, so I can explain some interresting or more difficult implementation detail.

4. Implementation

Most convenient and reliable way to create a remote object is to define some interface, and then implement it in a class derived from System.MarshalByRefObject.

I named the whole interface IRemoteFileLoader, just because the loading of files passed with the second-instance command line is the most essential function of it:

C#
interface IRemoteFileLoader {
    void HandleCommandLine(string[] commandLine);
    void ActivateFirstInstance();
    void TestInterface();
}

I hope the purpose of this interface is quite clear, except one member I have to explain, TestInterface. This method is designed to have implemention doing nothing at all, with emty body. Isn't it redundant? Almost. It's only purpuse is to... throw an exception when the remote object is absent, not registered. By design of remoting, the System.Activator object does not throw exception and returns a remote object proxy in all cases, even when connection is impossible. The exception is thrown only at the attempt to call any interface methods. Why not reusing other two methods for this purpose? The answer is simple: because we want them to be optional.

The implementation is non-public (internal) and used internally in the SingleInstanceManager facility. This is the complete implementation:

C#
internal class Server : System.MarshalByRefObject, IRemoteFileLoader {
    internal event System.EventHandler<CommandLineEventArgs> CommandLineHandling;
    internal event System.EventHandler FirstInstanceActivating;
    void IRemoteFileLoader.TestInterface() { }
    void IRemoteFileLoader.HandleCommandLine(string[] commandLine) {
        if (CommandLineHandling != null)
            CommandLineHandling.Invoke(this, new CommandLineEventArgs(commandLine));
    } //IRemoteFileLoader.HandleCommandLine
    void IRemoteFileLoader.ActivateFirstInstance() {
        if (FirstInstanceActivating != null)
            FirstInstanceActivating.Invoke(this, new EventArgs());
    } //IRemoteFileLoader.ActivateFirstInstance
    public override object InitializeLifetimeService() { return null; }
    //to keep it alive by ignoring life time lease policy
} //class Server

As one can see, the server remote object does not directly handle events from the second instance, it delegates implementation to the user. But its event membes are internal. The event handled by the SingleInstanceManager facility which, in turn, delegates the implementation to the application code, how it is shown in the usage section.

The interface/public part of the SingleInstanceManager facility should be apparent from the usage section: it has two public events, FilesLoading and FirstInstanceShowing, with two corresponding methods use to invoke them in the server (first instance) side from the client (second instance side). Besides, this is the main method used to start all that:

C#
public static class SingleInstanceManager {
    public static bool IsSecondInstance {
        get {
            bool secondInstance = DetectSingleInstance();
            if (!secondInstance)
                RegisterServerObject();
            return secondInstance;
        } //get IsSecondInstance
    } //IsSecondInstance
        
    // ...
    // implementation:
    static bool DetectSingleInstance() {
        try {
            var channel = new IpcClientChannel();
            ChannelServices.RegisterChannel(channel, false);
            RemoteFileLoader = (IRemoteFileLoader)Activator.GetObject(
                typeof(Server),
                RemoteObjectUrl);
            RemoteFileLoader.TestInterface();
        } catch { RemoteFileLoader = null; return false; }
        return RemoteFileLoader != null;
    } //DetectSingleInstance
    // ...
}

The only detail which really needs attention is the two names used in the remoting mechanism: channel name and remote proxy name, which also combine to create a required remote object URL. They have special uniqueness requirement. The uniqueness problem would also look pretty trivial if not another factor: correctness of the cross-platform implementation, which makes the whole issue quite delicate and requiring special consideration and care. This is the remaining implementation detail I want to discuss below.

5. Unique IPC Names

Basically, there are two independent names, channel name and the individual remote object proxy name. Having two strings, ChannelName and ProxyObjectName, we should have the URL of the following form:

C#
string.Format("tcp://{0}/{1}", ChannelName and ProxyObjectName);

There is nothing like this line in my code; I don't want to have a single hard-coded "magic string"; it would greatly compromise the maintenance and cross-platform qualities of the code. Please download and see the complete code to see where these strings are used; this is a general and usual remoting stuff.

I can see two possibilities for the unique names. Only the channel name has to be system-unique. One approach is to generate some per-application string which can be world unique with reasonably high probability. It could be GUID, or a high-size encryption key serialized into a string, or something like that. We don't really need anything world unique here; it would be preferable to have something system-unique, but with 100% probability. Besides, adding a hard-coded string to each application would be yet another development step, logically unnecessary (it is not related to any application requirements), and hence, yet another source of bugs. Can we have all strings generated automatically?

So, my other approach is use of the file name of the main executable module. Can it suit our purpose? Apparently, if one keeps the application in the same location in the file system, the other process started with this file will be detected and second one and will work as required. If the executable file is copied to some other location, two different processes can work independently. But should we consider it as a problem? What's wrong with that? Normally, we don't need to copy the executable file of any reasonably developed location. But if we do it, we do it consciously, for debugging, testing, or other special purposes, and enjoy two instanced running in parallel, but no more than that.

First, let' obtain this string, which can be done be finding of the entry assembly's location, System.Reflection.Assembly.GetEntryAssembly().

By the way, there are many other ways to get this string. However, I have a serious warning for those who use any other way: not all those ways will give you correct results in all cases; it's actually depends on how an application is hosted and other factors. Getting this string from Reflection gives the same results in all cases. Please see my past answers for some more detail:
How to find my programs directory (executable directory),
How to find my programs directory (current directory, "special folders").

Will it work? On Microsoft platform, yes, it will immediately work, but only because different formats of the path strings of Microsoft file systems and URI, first of all, different path delimiters, '\' and '/'. On *NIX systems, both separators are '/'; and it will mess up things. So, my solution is to modify this string by replacing the directory separator with the path separator ';' Why this specific character? Well, because it is declared in .NET FCL and because it's a known valid URI delimiter (of query parameters). So, the solution is:

C#
static string ChannelName {
    get {
        return System.Reflection.Assembly.GetEntryAssembly()
            .Location.Replace(
                System.IO.Path.DirectorySeparatorChar,
                System.IO.Path.PathSeparator);
    }
}

Now, another funny problem is also related to the goal to avoid hard-coded strings and is somewhat funny, because the implementation of System.Uri is not perfect (some other writers pointed it out) and does not provide the member calculating URI path separator '/'. It is designed to add it by combining URI from URI parts, but does not work correctly if some URI authority can be interpreted as file name; and it cannot also work with the scheme "ipc", so it results in "ipc:///*", instead of requred "ipc://*". The string "ipc" is also not defined explicitly, so one ridiculous way to obtaine is using the fake temporaty instance of System.Runtime.Remoting.Channels.Ipc.IpcChannel as new IpcChannel().ChannelName.

Overall, it all gave me rather ridiculous implementation of the URI name:

C#
static string RemoteObjectUrl {
    get {
        Func<string> getUriPathDelimiter = () => {
            Uri uri = new Uri(Uri.UriSchemeFile + Uri.SchemeDelimiter + ProxyObjectName);
            return uri.AbsolutePath;
        }; //getUriPathDelimiter
        string scheme = new IpcChannel().ChannelName;
        System.Text.StringBuilder sb = new System.Text.StringBuilder(scheme);
        sb.Append(Uri.SchemeDelimiter);
        sb.Append(ChannelName);
        sb.Append(getUriPathDelimiter());
        sb.Append(ProxyObjectName);
        return sb.ToString();
    } //get RemoteObjectUrl
} //RemoteObjectUrl

See also my short Tip/Trick article "Hide Ad-hoc Methods Inside the Calling Method’s Body".

It may look highly artificial, but again, avoiding of hard-coding magic strings highly improves maintenance and code portability.

6. Single-Instance Facility and Test/Demo Applications

I provided the single instance facility in a separate assembly, and two other assemblies, one for System.Windows.Forms and another one for WPF, all in one solution; the three projects referencing each other. Each of the demo applications creates a tab control and shows it when a second instance starts and sends its command line to the first instance, even if this command line is empty. If some command line parameters is an actually existing file, the first instance loads it and shows in a new tab page, focusing it. The activation is handled by showing the application on top of other application windows on the desktop in Z order.

There is only one feature in these demo applications which may need some explanation: the entry point of the WPF application. Apparently, the single-instance technique needs access to the entry point method of the application, Main. Not all WPF developers understand how to create such method explicitly, because such developers work only with auto-generated entry point and may not even know where to find it. Such developers need to look and the files of the WPF demo project, "EntryPoint.cs" and "Main\TheApplication.cs". The class TheApplication replaces the auto-generated Application and adds a number of important features, importantly, the universal exception handler used in the main event loop. This is my little bonus.

7. Build and Platform Compatibility

All projects are build in one click on the single batch file provided, build.bat. The code is built to the target of .NET Framework v.3.5, to have the "common denominator" which can be build on most Windows machines. The solution should work on .NET of any later version.

The core assembly, SingleInstance.dll is designed to be a cross platform. It should work on different CLR platforms (first of all, Mono) on different OS without recompilation. (Of course, WPF demo cannot be used on non-Microsoft platforms.)

However, I did not yet test it on any non-Microsoft platform. I always have at least one Linux machine for testing and will try to do it later, when some other projects will be ready. I will be much grateful if anyone takes the labor of testing of this facility and report any problems.

8. Afterword

I planned a short cycle on the application single-instance behavior. By this time, I envisioned only two articles. The next article, Single-Instance Applications Behavior, now for Free Pascal is devoted to cross-platform applications based on Free Pascal. This is a wonderful open-source cross-platform technology for development of native applications and other software solutions on amazingly wide set of target OS, wider than that of CLR, with wonderful cross-platform model for UI.

Today, this is the best native-code cross platform development technology I'm aware of. No wonder, my implementation of the application single-instance behavior looks amazingly compact, thanks to one available cross-platform messaging unit.

License

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