Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Single Instance Application in VB.NET

0.00/5 (No votes)
26 Mar 2003 2  
An article attempting to demonstrate how to get an application to behave as a Single Instance in .NET.

Sample Image - sing_inistan.jpg

Introduction

This article will attempt to show how to setup a VB.NET Windows application to become a Single Instance type application within the .NET Framework. Techniques used include Mutexes, .NET Remoting and Thread communications between Forms. Please note that I did not separate the functionality into separate classes to keep the code size down. Also note that I did not utilize any Imports statements because when working with classes I am unfamiliar with, I like to keep the full references in the code to get used to their placement in the .NET Framework.

Background

Recent work on a project presented a problem where we needed the main application to allow only one instance of the application to run at a time. It was also required that secondary instances that were started should pass the command line arguments to the running application to be processed.

Using the code

To use this code in your VB.NET application, simply add the SingleInstanceHandler.vb file to your project and add the following code to your startup code:

Module ModMain
    Private m_Handler As New SingleInstanceHandler()

    Public Sub Main(ByVal args() As String)
    'Add the startup call back

        AddHandler m_Handler.StartUpEvent, AddressOf StartUp 
        m_Handler.Run(args)
    End Sub
    
    Public Sub StartUp(ByVal sender As Object, 
                       ByVal event_args As StartUpEventArgs)
    End Sub
End Module

The StartUp function is called by the SingleInstanceHandler class whenever an instance of the application is started. The StartUpEventArgs has two interesting members: NewInstance is a boolean that indicates that this is the first instance of the application and Args is a string collection of the arguments passed in on the command line.

For my example project, I have a MDI form SingleInstanceForm that parents child forms that are created from the arguments that are passed in on the command line. The code is shown here:

Private m_MainForm As SingleInstanceForm        
        
Public Sub StartUp(ByVal sender As Object, ByVal event_args As StartUpEventArgs)
    ' This is the first instance, create the main form 

    ' and addd the child forms    

    If event_args.NewInstance Then 
        m_MainForm = New SingleInstanceForm()
        m_MainForm.AddChildForms(event_args.Args)
        Application.Run(m_MainForm)
    Else 
        ' This is coming from another instance, so simple add the child forms

        ' We are probably calling from a seperate thread, so we need to use 

        ' the forms Invoke command to ensure that we are perform the 

        ' AddForm method in the proper thread.

        Dim parameters() As Object = {event_args.Args}
        m_MainForm.Invoke(CType(AddressOf m_MainForm.AddChildForms, 
                        SingleInstanceForm.AddChildFormsDelegate), parameters)
    End If
End Sub

The SingleInstanceHandler class does the majority of its work through the Run method. This method is passed in the command line arguments as string collection. Here is the code for the Run method:

Private m_Mutex As System.Threading.Mutex
Private m_UniqueIdentifier As String
Private m_TCPChannel As System.Runtime.Remoting.Channels.Tcp.TcpChannel

Public Sub Run(ByVal args() As String)
    ' Create a Unique Identifier for the Mutex object and the 

    'SingletonCommunicator. We are using the executable path for the 

    'application because we want to ensure that the instances are unique 

    'based on installation locations.

    'Hack: Attempting to create a Mutex with an identifier that looks like a 

    'path

    ' will cause a path not found exception. So simply replace the '\'s with

    ' underscores and this should still be a unique identifier for this app

    ' based on the running location.

    m_UniqueIdentifier = Application.ExecutablePath.Replace("\", "_")

    ' Create the Mutex class
    m_Mutex = New System.Threading.Mutex(False, m_UniqueIdentifier)
    ' Attempt to lock the Mutex
   
    ' We locked it! We are the first instance!!!    
    If m_Mutex.WaitOne(1, True) Then 
        CreateInstanceChannel()
        ' Raise the StartUpEvent as a NewInstance
        Dim event_args As New StartUpEventArgs(True, args)
        RaiseStartUpEvent(event_args)
    Else 
        ' Not the first instance!!!
        ' Raise the StartUpEvent through the 
        ' SingletonCommunicator not as a NewInstance
        Dim event_args As New StartUpEventArgs(False, args)
        UseInstanceChannel(event_args)
    End If
End Sub

Please note that the m_UniqueIdentifier that I used here is the executable path of the application. We have a special constraint that requires the application be uniquely identified by installation location rather than version number and/or assembly name. It is easy to modify this identifier to be any other unique representation for this application.

The CreateInstanceChannel and UseInstanceChannel are used respectively to communicate between instances of the application. If the application was able to lock the m_Mutex object, it is obviously the first instance of the application running on that system, so it will call CreateInstanceChannel and then call RaiseStartUpEvent with a StartUpEventArgs object with NewInstance set to True. If unable to obtain the lock on the m_Mutex object, this is not the first instance of the application on the system, we will pass a StartUpEventArgs object to UseInstanceChannel with NewInstance set to False.

The CreateInstanceChannel method will marshal the SingleInstanceHandler class and uses .NET Remoting to create a TCP Channel that will be used by subsequent instances to communicate back to this instance. The URL for this TCP Channel is stored in the registry beneath the Application Data Registry section by the m_UniqueIdentifier.

Private m_TCPChannel As System.Runtime.Remoting.Channels.Tcp.TcpChannel        
        
Private Sub CreateInstanceChannel()
    System.Runtime.Remoting.RemotingServices.Marshal(Me, m_UniqueIdentifier)
    Dim tcp_properties As IDictionary = New Hashtable(2)
    tcp_properties("bindTo") = "127.0.0.1"
    tcp_properties("port") = 0
    m_TCPChannel = New System.Runtime.Remoting.Channels.Tcp.TcpChannel( _
                                             tcp_properties, Nothing, Nothing)
    System.Runtime.Remoting.Channels.ChannelServices.RegisterChannel( _ 
                                                                 m_TCPChannel)
    Dim key As Microsoft.Win32.RegistryKey = Application.UserAppDataRegistry()
    key.SetValue(m_UniqueIdentifier, 
                 m_TCPChannel.GetUrlsForUri(m_UniqueIdentifier)(0))
End Sub

The UseInstanceChannel will retrieve the URL for the TCP Channel from the Application Data Registry section by the m_UniqueIdentifier, and will use RemotingServices to connect to the SingleInstanceHandler class on the first instance, and call the RaiseStartUpEvent method with the passed in StartUpEventArgs.

Private Sub UseInstanceChannel(ByVal event_args As StartUpEventArgs)
    Dim key As Microsoft.Win32.RegistryKey = Application.UserAppDataRegistry()
    Dim remote_component As SingleInstanceHandler = CType( _
          System.Runtime.Remoting.RemotingServices.Connect(_
          GetType(SingleInstanceHandler), _
          key.GetValue(m_UniqueIdentifier)), SingleInstanceHandler)
    remote_component.RaiseStartUpEvent(event_args)
End Sub

The RaiseStartUpEvent method simply raises the StartUpEvent with the passed in StartUpEventArgs.

Public Sub RaiseStartUpEvent(ByVal event_args As StartUpEventArgs)
    RaiseEvent StartUpEvent(Me, event_args)
End Sub

Public Event StartUpEvent As StartUpEventArgsHandler

The StartUpEventArgs class is shown here. The class is marked as Serializable so that it can be sent through the TCP Channel. This class is simply designed to hold the NewInstance boolean and Args string collection. I did not make the member variable properties to keep the code size down.

<Serializable()> _
Public Class StartUpEventArgs
    Inherits EventArgs
    Public Sub New(ByVal new_instance As Boolean, ByVal the_args() As String)
        NewInstance = new_instance
        Args = the_args
    End Sub

    Public NewInstance As Boolean
    Public Args() As String
End Class

Points of Interest

One of the interesting errors that I ran across when developing this solution was an exception that occurred when the application tried to create additional child forms from a second instance; the following exception would occur:

SingleInstance Exception

Using the Invoke method ensures that proper thread is used to call methods within the forms classes so that the previous exception won't happen. The following code shows how the Invoke method is used to call the AddChildForms in the proper thread.

' We are probably calling from a seperate thread, so we need to use the forms 

' Invoke command to ensure that we are perform the AddChildForms method in 

' the proper thread.

Dim parameters() As Object = {event_args.Args}
m_MainForm.Invoke(CType(AddressOf m_MainForm.AddChildForms, _
                  SingleInstanceForm.AddChildFormsDelegate), parameters)

History

March 12, 2003 - Initial release of the article.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here