<

[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

x

Avoiding running multiple instances of an application

Published on
23,659 Points
9,059 Views
11 Endorsements
Last Modified:
Awarded
Community Pick
Jim Dettman (Microsoft MVP/ EE MVE)
Independent consultant specializing in the writing of custom packages for businesses.
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.
11
Comment
  • 7
  • 3
  • 2
  • +5
18 Comments
LVL 50

Expert Comment

by:DanRollins
Great article, JDettman!  Got my vote, above :-)
0
LVL 60

Author Comment

by:Jim Dettman (Microsoft MVP/ EE MVE)
Dan,
 
 Glad to hear that you enjoyed it.  Thanks.

JimD.
0
 

Administrative Comment

by:harfang
Afterthought: you wanted to tag all articles of your series, so the tags should read:

Tags: VBA, Access programming, application development, app development series

(or whatever name you choose for the series)

(°v°)
0
Protecting & Securing Your Critical Data

Considering 93 percent of companies file for bankruptcy within 12 months of a disaster that blocked access to their data for 10 days or more, planning for the worst is just smart business. Learn how Acronis Backup integrates security at every stage

Expert Comment

by:Juan Velasquez
I agree, it's a great article.  It really came in handy
0
LVL 60

Author Comment

by:Jim Dettman (Microsoft MVP/ EE MVE)

 Glad to hear you enjoyed the article.

JimD.
0

Expert Comment

by:Juan Velasquez
If I have some problems implementing the code how should I post them - as a new question or as a comment to this article
0
LVL 60

Author Comment

by:Jim Dettman (Microsoft MVP/ EE MVE)
Post as a new question please and point to the article.

JimD.
0

Expert Comment

by:dleads
What is "myapp" here? Is it the name of database such as Inventorry.mdb ?
0
LVL 60

Author Comment

by:Jim Dettman (Microsoft MVP/ EE MVE)

  "myapp" is just a short name or tag that you want to use to identify the application.

JimD.
0

Expert Comment

by:askolits
Great routine. Very helpful. Thanks!
0
LVL 60

Author Comment

by:Jim Dettman (Microsoft MVP/ EE MVE)
Glad you liked it and found it useful.

Jim.
0

Expert Comment

by:Mohamed Singh
Hi Jim,
Excellent article.
I'm basically following your articles as they relate to application development.

Just one question:
It would be great if you could kindly post the 64-bit API (If VBA7 Then.. Else...) ?
(If not, i'll work through it).

Kind Regards,
Mohamed
0
LVL 60

Author Comment

by:Jim Dettman (Microsoft MVP/ EE MVE)
Mohamed,

 Time is very short for me right now and 64 bit has just not caught on.  Even Microsoft itself still recommends 32 bit Office for users.  I myself have no clients using 64 bit either.

 I will put it on my list of things to do, but it will be on the very bottom of the list.

Jim.
0

Expert Comment

by:askolits
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

0

Expert Comment

by:Mohamed Singh
Thank You Jim for responding.
Kind Regards,
Mohamed
0

Expert Comment

by:Mohamed Singh
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
0
LVL 29

Expert Comment

by:IrogSinta
Great code and well written article!
0
LVL 60

Author Comment

by:Jim Dettman (Microsoft MVP/ EE MVE)
Glad you like it Ron.

Jim.
0

Featured Post

Why Diversity in Tech Matters

Kesha Williams, certified professional and software developer, explores the imbalance of diversity in the world of technology -- especially when it comes to hiring women. She showcases ways she's making a difference through the Colors of STEM program.

Join & Write a Comment

Visualize your data even better in Access queries. Given a date and a value, this lesson shows how to compare that value with the previous value, calculate the difference, and display a circle if the value is the same, an up triangle if it increased…
Wrapper-1-Query. Use an Excel function to calculate a column for an Access query. Part 1. Shows a query in Access that has a calculated column with the results of an Excel worksheet function. See how to call a wrapper function from a query, and …

Keep in touch with Experts Exchange

Tech news and trends delivered to your inbox every month