65.9K
CodeProject is changing. Read more.
Home

Single instance applications

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.93/5 (7 votes)

Feb 4, 2006

3 min read

viewsIcon

31230

downloadIcon

372

Implementing single instance applications with command line recieving in VB.NET

Introduction

Visual Studio 2005 brings new way of creating single instance applications via help of Application Framework. But to be able to use Application Framework, application must start with form. That's right - no support for tray-ed applications at this point.

To overcome this limitation one must implement its own multiple instance management. Most traditional usage is to use mutex for detecting second instance. Problem is that first instance does not know when second instance fires. To overcome this behavior we must implement some kind of communication mechanism between instances. This will allow us to use custom action when second instance fires - like opening our main application window.

Mutex & Named Pipe

Usual mechanism for determining existence of another instance is mutex object. And while mutex can help us to end second instance, for data transfer we must use some other methods. For this example we will use named pipes since they are present on every system we may use as platform for .NET programs. There is also one more advantage over remoting - no problems with firewall warnings. Sometime it takes too much effort explaining to customer why all firewall warnings are not bad.

Shared Sub New()
  Dim refIsCreated As Boolean
   _mtxFirstInstance = New Threading.Mutex(True, Name, refIsCreated)
   IsAlreadyStarted = Not refIsCreated
   If IsAlreadyStarted = False Then
     _thrThread.Start()
   Else
    Dim ms As New IO.MemoryStream()
    Dim bf As New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
    bf.Serialize(ms, New NewInstanceEventArgs(System.Environment.GetCommandLineArgs()))
   ms.Flush()
    Dim np As New NamedPipe(Name)
    np.OpenExisting()
    np.Write(ms.GetBuffer())
    np.Close()
    ms.Close()
   End If
End Sub

Since there is no built in support for named pipes, on of classes in highlander assembly does just that. Implementation of named pipe is story which we will not tell now, suffice to say that some Win32 API calls were needed. For what is worth, its name (and also name of mutex) we have chosen full path to application.

Private Shared ReadOnly Property Name() As String
   Get
     Return System.Reflection.Assembly.GetEntryAssembly.FullName.Replace("\", "/")
   End Get
End Property

First application instance creates new thread which is dormant for most of time so there is no performance hit (named pipe connect method is blocking one). Second instance tries to create mutex, sees that mutex is already alive and thus exits itself. Before exit it takes its command line arguments and serializes them to named pipe channel thus breaking connect block on first application instance. First instance reads data, deserializes it and fires NewInstanceDetected event after which named pipe connect is called again thus blocking that thread until another instance is fired.

Private Shared Sub Run()
   Dim np As New NamedPipe(Name)
   np.Create()
   Do
    np.Connect()
    Dim b As Byte() = np.Read()
    np.Disconnect()
    Dim ms As New IO.MemoryStream(b)
    Dim bf As New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
    Dim e As NewInstanceEventArgs = DirectCast(bf.Deserialize(ms), NewInstanceEventArgs)
    ms.Close()
    RaiseEvent NewInstanceDetected(Nothing, e)
   Loop
End Sub

Tray

In this example I have chosen tray application since there is no built in support for multiple instance handling in VB.NET 2005 for application that have something else than form as their entry point. Most useful behavior is to show main window and parse through command line arguments given. Here parsing is substituted for showing all received arguments in TextBox.

    AddHandler Highlander.Highlander.NewInstanceDetected, AddressOf NewInstanceDetected
   If Highlander.Highlander.IsAlreadyStarted = True Then End

Notice special handling for main form which includes its early creation. This is needed to facilitate invoke when new instance fires and code in shared sub New ensures that underlying window handle is really created.

    frmMain = New frmMain
   frmMain.CreateControl()
   frmMain.Handle.GetType()

Back in main class I have placed event handling and delegate for transferring that data in form. Delegate is very important since all data comes from different thread into application one (remember thread creation in Highlander class!). After we get call in our method based on delegate, we can access form safely. In this example here is where we placed filling of TextBox with command line from other instance.

Private Shared Sub NewInstanceDetected(ByVal sender As Object, ByVal e As Highlander.NewInstanceEventArgs)
  Dim method As New NewInstanceDetectedProcDelegate(AddressOf NewInstanceDetectedProc)
   Tray.Form.Invoke(method, New Object() {e.CommandLineArgs})
End Sub

 
Private Delegate Sub NewInstanceDetectedProcDelegate(ByVal args As String())
Private Shared Sub NewInstanceDetectedProc(ByVal args As String())
   Tray.Form.txtParameters.Lines = args
   Tray.Form.Show()
   Tray.Form.Activate()
End Sub

Conclusion

For better understanding of this process, look at given sources. Although this may seem too complicated to get into, once Highlander assembly is deployed, one must only include proper calls and event handling.