Single Instance Winform Issue by ThinqLinq

Single Instance Winform Issue

In the simplest terms, a single instance application is an application which will only display one copy to a user at any time. This can be simply accomplished by a calling application by checking to see if a process is already running. The System.Diagnostics.Process namespace offers a number of ways of getting a handle on an existing process, including GetProcessByName("ProcessName"), GetProcessById(MyId), and GetProcesses(). Once you have a handle on it, you can inquire about the process's status, Close it (CloseMainWindow) or terminate it (Kill). Unfortunately, or perhaps fortunately from a security perspective, you can not interact with the running process natively. Additionally, acquiring the correct process can be problematic when using XP fast user switching or Terminal Services in which multiple users can be running their own instance of an application on the same machine. Simply killing a process could be dangerous if you don't acquire the correct instance and thus could terminate an instance of a process another user is using.

In addition to the single instance requirement, the desired behavior is to have a running instance respond to outside requests. Simply put, MyApplication should to be able to DoSomething when TheirApplication tells it to. A common example would be Internet Explorer, which opens a new web site in an already open instance when clicking on a url from another application. Using ActiveX Executables made grabbing a running process and calling methods in it easy using CreateObject. Even better, CreateObject worked fine with Terminal Server giving the instance from the current user's environment.

With the release of .Net, Microsoft removed the concept of the ActiveX Exe. Unless you know the correct keywords to search on, finding a solution is not easy. After much searching, I decided to try looking under the covers of Whidbey (using Lutz Roder's Reflector) to try to identify Microsoft's solution for SingleInstance in Whidbey. I soon decided against pursuing this as I didn't want to try to reverse engineer Generics. (More on the Widbey implementation will come later).

The reflecting exercise did lead me to a number of samples by searching on SingleInstance, in particular a sample on CodeProject by Reto Ravasio peaked my interest. I took the liberty of re-writing his C# sample into VB.Net which is available here. The core concepts are as follows:

  1. Try to start the application.
  2. Try to get a Mutex for the application's thread.
  3. Check the CreatedNew parameter of the Mutex's constructor to determine if this is the first instance (for this user), if it is then:
    1. Create a new TCP channel passing 0 as a parameter to get an available channel, and open this as a remoting listener for subsequent requests.
    2. Register this channel into HKeyCurrentUser so that future requests from this user will get routed to the correct remoting instance.
    3. Hand off execution to your main form and let it process the command line args as necessary using Environment.GetCommandLineArgs()
  4. If this is not the first instance then:
    1. Get the previously entered channel from HKeyCurrentUser
    2. Hook into the remoted instance with Activator.GetObject
    3. Invoke the exposed method (ActivateInstance) passing it the current commandline arguments as a parameter
    4. The first instance will respond via it's handling of the InstanceActivate event.
    5. Kill the second instance.

Stepping through the code in EndInit in the sample will show both processes identified above. Special care needs to be taken in using the combination of technologies mentioned above, specifically Remoting, and Registry access which both require special Code Access Security handling.

Additionally, a minimum of 3 threads need to be synchronized when a second instance calls into the first: the thread the first instance of the form was created in, the thread of the remoting channel and the thread of the temporary second instance. To demonstrate this, place breakpoints on the following lines:

  1. The RaiseEvent InstanceActivate(Me, e) on SingleInstance.OnInstanceActivate (will be on the Remoting channel's event).
  2. The first line of ShowNewLabel on Form1
  3. The OnInstanceShutdown line of EndInit

Start the first instance through the VS IDE. Using the command line, start a second instance of the tester executable passing in the following parameter: "error". When the first breakpoint is hit, attach to the second instance of Form1 using Debug|Processes, find the non-shaded copy of the Tester process and click Attach. (You can't attach to the process prior to this point when running the program from the command line. Step through the code watching the thread window (Debug|Windows|Threads) and you will see 3 distinct thread ID's used.

Special care needs to be taken with these threads. Among the pitfalls, adding a control to a form from an outside thread will cause an exception. The sample application handles this issue by utilizing the special MethodInvoker delegate of the form using the Me.Invoke(Me.MethodInvokerDelegate) to make an outside method call execute on the form's thread (ME) rather than the calling thread. To assist, you can check the form's InvokeRequired property (not visible with intellisense) to see if the MethodInvoker is required.

SingleInstance in VB.Net 2.0

Life becomes much simpler with VB.Net 2.0. Actually it is as simple as opening the winform project property window and checking the SingleInstance option. The executable will automatically become single instance without any additional custom code. (A sample project is included in the linked download in the SingleInstance05 directory).

In order to handle the command line args that are passed in on a second instance, some additional code will be necessary. First, you click the option to show all files. Next, drill into the My Project node through Application.myapp into Application.Designer.vb. From here, add the following method:

Private Sub MyApplication_StartupNextInstance(ByVal sender As Object, ByVal e As Microsoft.VisualBasic.ApplicationServices.StartupNextInstanceEventArgs) Handles Me.StartupNextInstance
If TypeOf Me.MainForm Is Form1 Then
Dim Args(e.CommandLine.Count) As String
e.CommandLine.CopyTo(Args, 0) DirectCast(Me.MainForm, Form1).showArgs(Args)
End If
End Sub

The Form1 can then handle the args from a method with the following signature:
Friend Sub showArgs(ByVal e As String())

If you are interested in digging under the covers, use Reflector and drill into the following method: Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.Run. (This namespace changed between beta 1 and beta 2 and may change once the final version is released.) A quick investigation of the framework code vindicates the use of the Mutex and remoting. Additionally, notice the explicit permissions assertions to handle Code Access Security concerns. Interestingly, the cross threading issue seems to be automatically handled.

For those C# people out there, Pedro Silva has a blog entry detailing how to take advantage of the SingleInstance functionality otherwise available only in VB.

The number of technologies necessary to do such a simple thing as have an already running application "do something" when called by an external application is somewhat bewildering. Things will become much simpler with VB.Net 2.0, but one must wonder if simpler options exist. I'm still open to other options if anyone has a better recommendation.

Posted on - Comment
Categories: VB -
comments powered by Disqus