Single instance applications






2.93/5 (7 votes)
Feb 4, 2006
3 min read

31230

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.