VB.NET 2003 "Previous Instance" that doesn't rely on "FindWindow" API

Recently I had the chance to improve some old "previous instance" detection code to add the ability to activate the previous instance. The code I used to detect a previous instance looked like this:

 Imports System.Diagnostics
 Private Function PrevInstance() As Boolean
     If UBound(Diagnostics.Process.GetProcessesByName(System.Reflection.Assembly.GetExecutingAssembly.GetName.Name)) > 0 Then
         Return True
     Else
         Return False
     End If
 End Function

Looking at this code now I can see many ways to optimise it. It returns "True" if there's another instance of the program running. I usually make a call to "PrevInstance" after I show a splash screen, and if there's another instance I simply pop up a message box advising the user to switch with ALT+TAB, then close the current instance.

My enhancement was that I wanted to bring the previous instance to the foreground. To do that I need the window handle, and most of the code to get the window handle relies on the FindWindow API. The FindWindow API takes a window title - there's some code at Paul Laudeman's blog "Windows Forms Tip: Ensure only one instance of your application is running at a time" which uses a mutex to do this (this code also works perfectly, if you know the window title, which leads me to my next point).

If you change the window title and you don't know the exact window title, it is still possible to get a window handle using FindWindow based on the class name alone. However I would have had to loop through windows of the type "WindowsForms10.Window.8.app*" (Paul's original post has a "3" in place of the "*", I found I only got results when I used a "9" in place of the "*") to find my window. To further complicate matters, I have my splash screen showing, so it also has the window title that I'm looking for. There may have been a partial solution in VB6 at "Win32 Window Title and Class Name Demo" which has a "FindWindowLike" function which might have helped (I did not have to go that far, thankfully).

I eventually went back to my existing code to see what could be done and played with the properties of the "Process" class, and made the following changes:

 Imports System.Diagnostics
 Private Function PrevInstance(ByRef handle as IntPtr) As Boolean
     ' array of running processes with this assembly name
     Dim p() As Process = Nothing
     
     Try
         ' put the processes into an array
         p = Diagnostics.Process.GetProcessesByName(System.Reflection.Assembly.GetExecutingAssembly.GetName.Name())
         ' if there's more than one process with this name
         If p.Length > 1 Then
             ' get the handle of the main window of the first running instance
             handle = p(0).MainWindowHandle
             ' program is running...return True
             PrevInstance = True
         Else
             PrevInstance = False
         End If
         
     Catch ex As System.Exception
         ' set return to False
         handle = IntPtr.Zero
         PrevInstance = False
         
     Finally
         ' clean up
         p = Nothing
     End Try
 End Function

Note: as you can see, my above code uses reflection, which incurs a performance hit. All valid comments/criticisms accepted.

The main change I made was to return a ByRef handle to the main window of the previous instance using the "MainWindowHandle" property. Easy.

I also have a function called "ShowPreviousInstance" which takes the window handle:

 ' parameter for "nCmdShow" for ShowWindow
 Private SW_RESTORE As Integer = 9
 
 ''' <summary>
 ''' Is the passed window handle minimised to the taskbar?
 ''' http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/windows/windowreference/windowfunctions/isiconic.asp
 ''' </summary>
 Private Declare Auto Function IsIconic Lib "user32" (ByVal hWnd As IntPtr) As IntPtr
 
 ''' <summary>
 ''' Sets a passed window to be in the foreground.
 ''' http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/windows/windowreference/windowfunctions/setforegroundwindow.asp
 ''' </summary>
 Private Declare Auto Function SetForegroundWindow Lib "user32" (ByVal hwnd As IntPtr) As Long
 
 ''' <summary>
 ''' Show a window with the passed command (state)
 ''' http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/windows/windowreference/windowfunctions/showwindow.asp
 ''' </summary>
 Private Declare Auto Function ShowWindow Lib "user32" (ByVal hWnd As IntPtr, ByVal nCmdShow As Integer) As IntPtr

 Private Function ShowPreviousInstance(ByVal handle As IntPtr) As Boolean
     
     ' if we got a window of the currently-running instance
     If handle.ToInt32 <> IntPtr.Zero.ToInt32 Then
         
         Try
             ' if the window IsIconic (is in the taskbar), restore it
             If IsIconic(handle).ToInt32 <> 0 Then
                 ShowWindow(handle, SW_RESTORE)
             End If
             
             ' make foreground window
             SetForegroundWindow(handle)
             
             ' return True to say we switched to the previous instance
             ShowPreviousInstance = True
             
         Catch ex As System.Exception
             ' return false
             ShowPreviousInstance = False
         End Try
     Else
         ' no valid handle passed, return false
         ShowPreviousInstance = False
     End If
     
 End Function

If "ShowPreviousInstance" returns True, I close down the new instance of the application.

I like this code because it's a little shorter than some of the other samples I've seen, and it doesn't use a lot of API calls. The ultimate ease-of-use setting is coming in Visual Basic 2005, but this code is going to do until then.

UPDATE: for this code to work, you need to call Application.Run with an Application COntext, and not just a form. Here's some democode that supplements my original code above:

    '(in module)
    ' private instance of Main form
    Private m_frmMain As frmMain ' **** change to your main form here ****
    ' private Application context
    Private m_appContext As ApplicationContext
    
    <STAThread()> _
    Public Sub Main()
        ' initialise Application context
        m_appContext = New ApplicationContext
        
        ' process handle for currently-running application
        Dim handle As IntPtr = IntPtr.Zero
        ' is the program currently running, and have we activated it?
        Dim blnQuit As Boolean = False
        
        ' check for previous instance
        If PrevInstance(handle) Then
            MessageBox.Show("App is already running!")
            
            ' did we get a handle from "PrevInstance"?
            If handle.ToInt32 <> IntPtr.Zero.ToInt32 Then
                
                ' check if we can show the window, and if so, set quit flag to true
                blnQuit = ShowPreviousInstance(handle) 
                
            End If
        End If
        
        If Not blnQuit Then
            ' set up main form
            m_frmMain = New frmMain ' **** change to your main form here ****
            ' show it
            m_frmMain.Show()
            
            ' **** this line sets the main window, allowing process "MainWindowHandle" call to work! ****
            m_appContext.MainForm = m_frmMain
            ' run the application context
            Application.Run(m_appContext)
            ' Application will exit when closing main Form
            
        End If
        
        ' clean-up code can go here
        
    End Sub
«May»
SunMonTueWedThuFriSat
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234