Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Limiting an Application to a Single Instance - The MFC Way

0.00/5 (No votes)
30 Aug 2002 3  
Another approach for limiting application instance
In this article, you will find another approach to limit application to a single instance in the MFC way.

Introduction

When I first posted the article, I wrote: "I have seen many samples on limiting application instance but some of them were too complicated while the others required multiple additions in different classes because of message handling on window level." Well, some time ago, I understood that it is better not to make statements like that because I actually faced so much problems that I lost myself maybe not in the complexity but in the amount of code which I wrote trying to solve them and limit that damned application to a single instance. :) I wanted to have all realization in one class only and handle the messages on the application level, not windows. Note - VB 6.0 users have the simplest solution - they can check App.PrevInstance to limit instance, it's real and then ActivateApp or something like that.

Usage

Derive the application class from CWinAppEx and just check InitInstance() function's (with some GUID in parameter) return value and exit instance in case it is FALSE. Also, add EnableShellOpen and RegisterShellFileTypes for SDI and MDI applications. That is all. (The previous instance will popup the main window itself if you won't override OnAnotherInstance virtual function to do some specific actions when another instance is started). Note: If lpszUID is not specified in the InitInstance(), the instance will not be limited.

//...		
BOOL CSomeApp::InitInstance( void )
{
    // CSomeApp is derived from CWinAppEx
    
    if( CWinAppEx::InitInstance( _T( "{6BDD1FC6-...}" ) ) )
        return FALSE;
    
    AfxEnableControlContainer();
    
    //...
    
    // Enable DDE Execute open
    EnableShellOpen();
    RegisterShellFileTypes( TRUE );
    
    //...
    
    return TRUE;
}
//...

Some History

Once in the MSDN, I found the BroadcastSystemMessage function: MSDN: "The BroadcastSystemMessage function sends a message to the specified recipients. The recipients can be applications, installable drivers, network drivers, system-level device drivers, or any combination of these system components". Well, it allowed handling the messages on the application level and create this class.

Description

If you look in the sources, you'll see that in the InitInstance, the application registers app-unique message with UID passed and holds the value returned in the private variable to identify another instance messages sent later. Then it searches for another instance using (tradition) mutexes (FindAnotherInstance function) and in case it finds it, broadcasts the unique message using BroadcastSystemMessage (PostInstanceMessage function) and exits. App. previous instance (if any) receives the message, compares it with own one and if they are equal, calls virtual function OnAnotherInstanceMessage, whose default implementation popups the main window. (These steps are required for dialog based applications and for SDI and MDI apps when launching application executable (not associated document) and in some cases that are described later).

The BroadcastSystemMessage required SE_TCB_NAME privilege enabled on NT to send message to all desktops, so I wrote EnableTokenPrivilege function, that enables or disables privileges in the specified access token, which is called before broadcasting the message. I thought that it can be useful also outside this class and placed only its code in the article body removing the old code pieces, because the sources are totally overwritten and appeared to be too large for this article. To look at the implementation, you should open the source files.

//...
BOOL CWinAppEx::EnableTokenPrivilege( LPCTSTR lpszSystemName, 
                                      BOOL    bEnable /*TRUE*/ )
{
    ASSERT( lpszSystemName != NULL );
    BOOL bRetVal = FALSE;
    
     if( ::GetVersion() < 0x80000000 ) // NT40/2K/XP // afxData ???
    {
        HANDLE hToken = NULL;		
        if( ::OpenProcessToken( ::GetCurrentProcess(), 
            TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken ) )
        {
            TOKEN_PRIVILEGES tp = { 0 };
            if( ::LookupPrivilegeValue( NULL, lpszSystemName, &tp.Privileges[0].Luid ) )
            {
                tp.PrivilegeCount = 1;
                tp.Privileges[0].Attributes = ( bEnable ? SE_PRIVILEGE_ENABLED : 0 );
								
                // To determine whether the function adjusted all of the
                // specified privileges, call GetLastError:
    
                if( ::AdjustTokenPrivileges( hToken, FALSE, &tp, 
                    sizeof( TOKEN_PRIVILEGES ), (PTOKEN_PRIVILEGES)NULL, NULL ) )
                {
                    bRetVal = ( ::GetLastError() == ERROR_SUCCESS );
                }
            }
            ::CloseHandle( hToken );
        }
    }

    return bRetVal;
}
//...

Many people complained about the lack of DDE support. They have had problems opening documents directly from explorer double-clicking them - the old implementation was just limiting its instance in any case. MDI applications have DDE support, so the user has nothing to do for limiting the application instance when double-clicking associated document, while SDI hasn't.

Thinking how to solve that, I eventually stumbled on the reg file which is automatically generated for SDI apps (it contains all document registration) and is included into the projects - and that was the solution! I overrode CWinApp::RegisterShellFileTypes to perform DDE registration for SDI apps also. If we look in the code we'll see that first MFC registers the file types and then an additional loop is performed over all document types registering DDE. This code was also large enough to be placed here so you have to open the source files again. Now if user clicks the associated document, the SDI application opens it in the same instance, just the way MDI app does. The operating system handles that without any help from the program.

But there is one more problem - if we run the application with the associated document in parameter from the windows run dialog, then no DDE will save us and a new instance will be started. To correct that, application passes document file name to another instance and exits only if command line contains FileNew or FileOpen parameters, in other cases such as FilePrint, etc., it does not exit instance because framework will process these commands in new instance in invisible mode and will exit itself.

Notes

Registry DDE

When testing RegisterShellFileTypesEx again under XP, and then clicking the associated file, OS gave me some strange message that the associated file is not found and I should search for it, though OS opened it normally. (I remembered that it ran normally at work under 2000 and Windows 98). Cursing my code and everything, I began to compute what could cause that error, and checking registry values, removed one key by chance, after which all has gone perfect. If we look in the registry under the application document's key "ddeexec" key, we'll find "application" key. The reg file told us that the "application" key is optional; it defaults to the app name in "command" key. Well, I added XP version check that does not add any "Application" key and even removes it if present in case of XP OS. But frankly speaking, I do not understand why it gives that error, and would be very grateful to anyone who will explain it.

CWinAppEx Class Members

Data Members

m_uMsgCheckInst Holds application unique message ID

Operations

PostInstanceMessage Posts a message to another instance of the application (if any)
EnableTokenPrivilege Enables or disables privileges in the specified access token (Windows NT only)
RegisterShellFileTypes Registers all of your application's document types with the Windows File Manager, also performs additional registration for SDI applications
UnregisterShellFileTypes Unregisters all of your application's document types with the Windows File Manager, also unregisters additional data required for SDI applications

Overridables

InitInstance Initializes new instance of your application running, if has parameter specified (GUID) then returns FALSE if any other instance of application with this UID is found.
OnAnotherInstanceMessage Application receives this message from another started instance. The default implementation is as follows - the started application sends this message (with its command line if needed) to the previous one, and the previous instance receives it, processes the command line (if any) and popups the main window in this function.

Globals

GetApp Obtains a pointer to the CWinAppEx object

Version History

  • 21st May, 2002: Posted article
  • 27th August, 2002 - Mostly rewritten code, added DDE support for SDI applications, added token privilege adjustment

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here