Community Pick: Many members of our community have endorsed this article.
Editor's Choice: This article has been selected by our editors as an exceptional contribution.

Avoiding running multiple instances of an application

Jim Dettman (EE MVE)Volunteer
CERTIFIED EXPERT
Independent consultant specializing in the writing of custom packages for businesses.
Published:
Updated:
This article is part of the app development series, a series of articles on Experts-Exchange.com that explores common application development problems and their solutions.

This article presents code written in VBA executing on Windows platforms, but the techniques demonstrated here could be used with any language or application.

Level: Intermediate

THE PROBLEM

One common problem in development is how to prevent users from running multiple copies of an application.  In some cases users need to be allowed to do this, but in others they need to be restricted from doing so.  Sometimes it's simply a performance issue that dictates this.   It might also be a development one (the app will not function correctly if multiple instances are run on the same station).   It may be only to help a user out.  Users will often start an application again and again only because they cannot locate an application that is already running (an application window may be hidden behind another or be off screen).

To be able to prevent multiple instances from running, we'll need to use a semaphore (a flag) to signal when our app is running.  This flag needs to be visible to every process running on a station.  Often, developers will try and create such a flag by saving a file on disk, setting an entry in the registry, or saving data in a database or file.  This flag gets set when a user starts the application and removed when the user exits.  Sounds like that would work right?

Of course in the real world all types of things can happen; impatient users issuing a Ctrl/Alt/Del, a station locking up, power loss with no UPS, etc., leaving the app to terminate abnormally and as a result; your flag still set.  Now when the user returns, their told the application is already running.  Ouch.  A reboot won't fix it either.  The only way to get this to work then is to give the user some type of utility to clear the flag and instructions on how to use it.  Problem is, this is something that might not happen very often and undoubtedly you're going to get a phone call when it does because they have forgotten all about that utility and how to use it.

THE SOLUTION

The way around that problem is through the use of a Mutex (Mutually Exclusive) object.  The job of a Mutex in an OS is to prevent multiple processes from accessing the same resource at the same time.  Every OS has something that serves in this function.   By exploiting that, we can handle all of the problems above.

You see, the beauty of a Mutex is that because it is held in memory through the OS, a reboot of the machine gives you a clean slate.  And as most developers know, if a user knows to do anything, it's to try a reboot first.   Even better, a Mutex is owned by a given thread and if the thread dies then typically so does the Mutex. This means that often a reboot is not even required; they just need to restart the app.

Here's a link to a MSDN (Microsoft Developers Network) article that goes into a little more detail on a Mutex:

http://msdn.microsoft.com/en-us/library/ms684266(VS.85).aspx

As the article explains, one uses the CreateMutex() API call in Windows to create a Mutex.  So with that as a starting point, let's take a look at some code!

First, since we are calling Window API functions, we need a declaration (a Declare statment) to access that function in Windows:

Private Declare Function CreateMutex Lib "kernel32" Alias "CreateMutexA" (lngMutexAttributes As Long, lngInitialOwner As Long, ByVal lpName As String) As Long

Open in new window


You really don't need to know a lot about this, just that it allows you to call the CreateMutex() and pass it some arguments.  Just paste it into a general module and your set.  Now we can write some VBA code like this:

Dim lngMutexHandle as long
                      
                      ' Create a muxtex object
                      lngMutexHandle = CreateMutex(0, 1,"myApp")

Open in new window


The first argument is used to apply security attributes to the Mutex, which we don't need for this, so we just set it to 0.  The second argument is a true / false flag for indicating if the thread issuing the call should be made owner of the object.  Since this is what we want, it is set to true.  This gives us ownership and no other process can take ownership of the mutex.  The finial argument is the name for the Mutex object.  This can be any string and should be application specific.  I typically use my application name; that is the name that I want to appear on the menu's, top of reports, displayed in dialog titles, etc. i.e. "Incentive System".  Again, this can be any text string.

After we issue this call, one of two things will happen:

1.  The Mutex will be created because this is the first instance of "myApp"
2.  We will get back a handle to an already existing Mutex called "myApp".  This means that another copy of the app is already running.

But how exactly do we know that?  The second will return an error, which in VBA can be checked through the err.LastDLLError property:

' Did we get a new instance or a handle to an existing one?
                      If Err.LastDllError = ERROR_ALREADY_EXISTS Then
                         ' App is already running
                         ' Message user and quit.
                         MsgBox "This application is already running on this station and multiple instances are not allowed", vbOKOnly+vbCritical
                         ' Call your exit routine here to quit the app.
                      Else
                         ' App is not running - OK to continue
                      End If

Open in new window


So if we do get back an error code that the Mutex already exists, then another instance of "myApp" is running, otherwise its not.  Note that ERROR_ALREADY_EXISTS is a constant, which is defined like this:

' Used for semephore check.
                      Const ERROR_ALREADY_EXISTS = 183&

Open in new window


We could have used the value directly in the If check like this:

If Err.LastDllError = 183& Then

Open in new window


But using the constant makes the code easier to read and less confusing down the road when you go back to it.  Like a declaration (Declare) statement, this gets put in the declarations section of a general module in VBA.

You now have the guts of it; trying to create a Mutex, checking if that happened, and then doing something based on that.  You could stop here, but just displaying a message to the user might leave them confused, especially if the first app instance is hidden from view.   What would be really nice is if we could switch them to that other instance without them having to do anything.

A LITTLE MORE FLEXIBILITY

How can we accomplish that?  Well you probably guessed it, we just need a few more API calls!   Now don't groan, it hasn't been that tough so far has it?   Just keep reading and we'll be done before you know it!  

The Windows API routines contain a function called GetWindow(), which lets us enumerate (loop through) all the windows that are currently running.   But how do we know which window is ours?  After all, there may be many applications running.  You could look at the .EXE name of the program itself that is executing in a thread, but what if someone runs something with the same .EXE (ie. Excel) that is un-related to your app?  We could look at the window title, but often developers like to change the window titles on the fly for various reasons.  Might there be a better way?  Yes there is.  As it turns out, Windows let's you add properties to a window.  All you need to do is give it a name and a value for the property.  With this, we can "mark" our window so we can find it again.  

To do that, add the following:

Private Declare Function SetProp Lib "user32" Alias "SetPropA" (ByVal hwnd As Long, ByVal lpString As String, ByVal hdata As Long) As Long

Open in new window


And now our code will look like this:

 Public Function AppAlreadyUp(bAllowMultipleInstances As Boolean) As Long
                      
                            Dim lngMutexHandle as long
                            Dim lngReturn as long
                      
                            ' Create a muxtex object
                            lngMutexHandle = CreateMutex(0, 1, myApp)
                      
                            ' Did we get a new instance or a handle to an existing one?
                            If Err.LastDllError = ERROR_ALREADY_EXISTS Then
                              ' App is already running
                              If bAllowMultipleInstances = False Then
                                ' Message user and quit.
                                MsgBox "This application is already running on this station and multiple instances are not allowed", vbOKOnly+vbCritical
                                ' Call your exit routine here to quit the app.
                              Else
                                 MsgBox "Warning, this application is already running on this station."
                            Else
                              ' App is not running - OK to continue
                              ' Mark the window with a property so we can find it again if we need to.
                              lngReturn = SetProp(Application.hWndAccessApp, "myApp", 1)
                            End If
                      
                      End Function

Open in new window


What SetProp() is doing here is adding the property "myApp" to the current threads window and assigning it a value of 1.  For what we are trying to do, the value is not important, just the property name itself.  Now this window is marked and we can easily locate it again outside of anything else.    Also note that I've added an argument, bAllowMultipleinstances for a little more flexibility in the procedure.  If this is set to True, a user just gets a warning when there is a copy of the application already running and they are allowed to continue.  If False they get a different message and cannot.

WRAPPING IT ALL UP

Now the last bit to take care of switching to the first copy if multiple instances are not allowed:

Add these declarations:

Private Declare Function BringWindowToTop Lib "user32" (ByVal lngHWnd As Long) As Long
                      Private Declare Function CloseHandle Lib "kernel32" (ByVal lnghObject As Long) As Long
                      Private Declare Function GetDesktopWindow Lib "user32" () As Long
                      Private Declare Function GetProp Lib "user32" Alias "GetPropA" (ByVal lngHWnd As Long, ByVal lpString As String) As Long
                      Private Declare Function GetWindow Lib "user32" (ByVal hwnd As Long, ByVal nRelationship As Long) As Long
                      Private Declare Function ShowWindow Lib "user32" (ByVal hwnd As Long, ByVal nCmdSHow As Long) As Long

Open in new window


And these constants:

' For GetWindow
                      Const GW_HWNDNEXT = 2
                      Const GW_HWNDChild = 5
                       
                      ' For ShowWindow
                      Const SW_MAXIMIZE = 3
                      Const SW_SHOWNORMAL = 1
                      Const SW_SHOWMINIMIZED = 2

Open in new window


And finally, the complete function:

Public Function AppAlreadyUp(bAllowMultipleInstances As Boolean, bDisplayMsg As Boolean) As Long
                       
                            ' Function checks for multiple instances of an application
                            ' by creating a mutex object.  If no error, then this is only
                            ' instance running.
                       
                            Const RoutineName = "AppAlreadyUp"
                            Const Version = "1.0"
                       
                            Dim lngMutexHandle As Long
                            Dim lngHWnd As Long
                            Dim lngReturn As Long
                            Dim strMsg As String
                       
                            On Error GoTo AppAlreadyUp_Error
                       
                            ' Create a muxtex object
                            lngMutexHandle = CreateMutex(0, 1, "myApp")
                       
                            ' Did we get a new instance or a handle to an existing one?
                            If Err.LastDllError = ERROR_ALREADY_EXISTS Then
                              ' App is already running
                              If bDisplayMsg = True Then
                                ' Close the handle just created as
                                ' it only points to the existing muxtex
                                lngReturn = CloseHandle(lngMutexHandle)
                       
                                If bAllowMultipleInstances = False Then
                                  strMsg = "This application is already running on this workstation.  You cannot start another copy."
                                  strMsg = strMsg & vbCrLf & "You will be switched to the existing copy."
                                Else
                                  strMsg = "Warning: This application is already running on this workstation."
                                End If
                                MsgBox strMsg, vbOKOnly + vbCritical
                              End If
                       
                              If bAllowMultipleInstances = False Then
                                ' Find the existing instance, switch to it, then close this instance
                                lngHWnd = GetWindow(GetDesktopWindow(), GW_HWNDChild)
                       
                                Do While lngHWnd > 0
                                  If GetProp(lngHWnd, "myApp") = 1 Then
                                    BringWindowToTop (lngHWnd)
                                    lngReturn = ShowWindow(lngHWnd, SW_MAXIMIZE)
                                    Exit Do
                                  End If
                                  lngHWnd = GetWindow(lngHWnd, GW_HWNDNEXT)
                                Loop
                       
                                ' Call application exit routine here or just quit your app.
                              End If
                       
                            Else
                              lngReturn = SetProp(Application.hWndAccessApp, "myApp", 1)
                            End If
                       
                      AppAlreadyUp_Exit:
                            On Error Resume Next
                       
                            ' Always return false just to pass something back
                            AppAlreadyUp = False
                       
                            Exit Function
                       
                      AppAlreadyUp_Error:
                            UnexpectedError ModuleName, RoutineName, Version, Err.Number, Err.Description, Err.Source, VBA.Erl
                            Resume AppAlreadyUp_Exit
                       
                      End Function

Open in new window


A few notes on the final code:
I added the argument bDisplayMsg, which controls if the user is messaged or not.  That combined with bAllowMultipleInstances will handle all the situations I outlined at the start of the artice.
Note the code in lines 37 through 48.  This is the code to loop through all the windows, find our marked window, and switch to it.
On line 44, the found window is maximized.  You can use the SW_SHOWNORMAL or SW_ SHOWMINIMIZED  constants if you wish instead of SW_SHOWMAXIMIZED.
The code on line 26 is a cleanup task.  When you attempt to create a mutex object, if it is not created, a Windows handle is returned to you along with the error.  This handle points to the already existing Mutex and because we are quitting, it will not be needed.  So we close the handle.
The UnexpectedError call on line 66 is a call to an error handling routine that I use.  Make sure you change this to either a Msgbox call or your own error handling routine.
Constants RoutineName and Version are used by my error handling routine to identify where problems occur because VBA does not expose through any property what procedure is currently executing.
I wrote this as a function, but there is no reason it could not be written as a sub.

  I have been using this code for a number of years and have not found any issues with it running on any Windows platform.   I hope you find it useful in your own applications.
12
11,642 Views
Jim Dettman (EE MVE)Volunteer
CERTIFIED EXPERT
Independent consultant specializing in the writing of custom packages for businesses.

Comments (17)

Commented:
Let me know if this works for you.


 
Option Compare Database
Option Explicit
#If VBA7 Then
    Private Declare PtrSafe Function SetProp Lib "user32" Alias "SetPropA" (ByVal hWnd As Long, ByVal lpString As String, ByVal hdata As Long) As Long
    Private Declare PtrSafe Function CreateMutex Lib "kernel32" Alias "CreateMutexA" (lngMutexAttributes As Long, lngInitialOwner As Long, ByVal lpName As String) As Long

    Private Declare PtrSafe Function BringWindowToTop Lib "user32" (ByVal lngHWnd As Long) As Long
    Private Declare PtrSafe Function CloseHandle Lib "kernel32" (ByVal lnghObject As Long) As Long
    Private Declare PtrSafe Function GetDesktopWindow Lib "user32" () As Long
    Private Declare PtrSafe Function GetProp Lib "user32" Alias "GetPropA" (ByVal lngHWnd As Long, ByVal lpString As String) As Long
    Private Declare PtrSafe Function GetWindow Lib "user32" (ByVal hWnd As Long, ByVal nRelationship As Long) As Long
    Private Declare PtrSafe Function ShowWindow Lib "user32" (ByVal hWnd As Long, ByVal nCmdShow As Long) As Long
#Else
    Private Declare Function SetProp Lib "user32" Alias "SetPropA" (ByVal hWnd As Long, ByVal lpString As String, ByVal hdata As Long) As Long
    Private Declare Function CreateMutex Lib "kernel32" Alias "CreateMutexA" (lngMutexAttributes As Long, lngInitialOwner As Long, ByVal lpName As String) As Long

    Private Declare Function BringWindowToTop Lib "user32" (ByVal lngHWnd As Long) As Long
    Private Declare Function CloseHandle Lib "kernel32" (ByVal lnghObject As Long) As Long
    Private Declare Function GetDesktopWindow Lib "user32" () As Long
    Private Declare Function GetProp Lib "user32" Alias "GetPropA" (ByVal lngHWnd As Long, ByVal lpString As String) As Long
    Private Declare Function GetWindow Lib "user32" (ByVal hWnd As Long, ByVal nRelationship As Long) As Long
    Private Declare Function ShowWindow Lib "user32" (ByVal hWnd As Long, ByVal nCmdShow As Long) As Long


#End If


' For GetWindow
Const GW_HWNDNEXT = 2
Const GW_HWNDChild = 5

' For ShowWindow
Const SW_MAXIMIZE = 3
Const SW_SHOWNORMAL = 1
Const SW_SHOWMINIMIZED = 2

Const ERROR_ALREADY_EXISTS = 183&

Open in new window

Thank You Jim for responding.
Kind Regards,
Mohamed
Hi askolits,
Thank You for taking the time to respond and for providing a solution.
I will revert to you soonest I can test your code.
Kindest Regards,
Mohamed
CERTIFIED EXPERT

Commented:
Great code and well written article!
CERTIFIED EXPERT
Fellow
Most Valuable Expert 2017

Author

Commented:
Glad you like it Ron.

Jim.

View More

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.