If App.PrevInstance Then
MsgBox “You cannot start more than one copy"
End
End If
If App.PrevInstance Then
Dim sTitle As String
‘ Save my title
sTitle = Me.Caption
‘ Change my title bar so I won’t activate myself
Me.Caption = Hex$(Me.hWnd)
‘ Activate other instance
AppActivate sTitle
‘ Terminate myself
End
End If
Dim hWndOther As Long
hWndOther = GetFirstInstWnd(Me.hWnd)
If hWndOther <> hNull Then
‘ Uncomment this line for debugging
‘MsgBox “Activating first instance"
SetForegroundWindow hWndOther
End
End If
Function GetFirstInstWnd(hWndMe As Long) As Long
Dim hWndYou As Long, idMe As Long, sExeMe As String
' Get my own process ID and executable name
idMe = MWinTool.ProcIDFromWnd(hWndMe)
sExeMe = ExeNameFromWnd(hWndMe)
' Get first sibling to start iterating top-level windows
hWndYou = GetWindow(hWndMe, GW_HWNDFIRST)
Do While hWndYou <> hNull
' Ignore if process ID of target is same
If idMe <> MWinTool.ProcIDFromWnd(hWndYou) Then
' Ignore if module name is different
If sExeMe = ExeNameFromWnd(hWndYou) Then
' Return first with same module, different process
GetFirstInstWnd = hWndYou
Exit Function
End If
End If
' Get next sibling
hWndYou = GetWindow(hWndYou, GW_HWNDNEXT)
Loop
End Function
This technique works for most applications, but it’s not infallible. Your program might have multiple top-level windows, and the first one returned might not be the one you want to activate. That’s why MODTOOL.BAS also contains the GetAllInstWnd function, which returns a Collection of all the window handles of other instances. GetAllInstWnd enables you to follow one of Joe Hacker’s favorite design rules: “If in doubt, let the user decide.” You could create a dialog box form with a list box of existing instances. Let the user decide whether to cancel the request, launch a new instance, or activate an existing one. This would be an excellent way to handle the new multiple instance model that is becoming popular as an alternative to the Multiple Document Interface—each document is handled by a separate independent instance of a small program rather than by a separate MDI window of a large program.
WARNING Terminating a duplicate instance is one of those rare cases when you should actually use the End statement. Although the name sounds harmless, End is actually more like an Abort statement that means stop by any means even if it can’t do normal cleanup. Experienced Visual Basic programmers terminate their programs by unloading all the active forms. Normally, all it takes is an Unload Me statement in the main form. But if you’re trying to terminate in the main Form_Load, there is, by definition, nothing to unload. So the rule is simple: Never use End except in Form_Load. In the first edition of this book, I violated this rule by providing a procedure that looked for a duplicate instance and terminated that instance with an End statement inside the procedure. I got a rude surprise when I tried to put that procedure in the VBCore component. Visual Basic won’t let you use the End statement in a DLL or a control. The GetFirstInstWnd function can be in a DLL because it returns the other instance rather than trying to take action about it. This is a more flexible structure anyway. The caller of GetFirstInstWnd can decide what it wants to do about the duplicate instance.