Click here to Skip to main content
Click here to Skip to main content

Limiting an application to a single Instance - the MFC way

, 30 Aug 2002
Rate this:
Please Sign up or sign in to vote.
Another approach for limiting application instance

Introduction

When 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 actually faced so much problems that I lost myself maybe not in the complexity but in the amount of code which wrote trying to solve them and limit that damned application to a single instance. Smile | :) 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 smth. 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 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 thye 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 inlcluded 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 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

  • 21-May-2002 - Posted the article.
  • 27-Aug-2002 - Mostly rewritten code, added DDE support for SDI applications, added token privilege adjustment.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Armen Hakobyan
Software Developer (Senior) SafeNet Inc
United States United States
No Biography provided

Comments and Discussions

 
QuestionRe: strange results after double-clicking in Windows Explorer PinmemberMichael Adamus17-Nov-09 11:10 
Generalif( CWinAppEx::InitInstance... should be if( !CWinAppEx::InitInstance... PinmvpIain Clarke10-Aug-09 0:54 
GeneralGreat work and the easiest one to use in CP PinmemberRay Guan11-Jun-09 18:01 
QuestionDialog Based on WinXP fast switch user mode Pinmembersanjay kaledhonkar4-Apr-07 5:14 
AnswerRe: Dialog Based on WinXP fast switch user mode PinmemberArmen Hakobyan4-Apr-07 20:42 
Avoiding Multiple Instances of an Application[^]
 
#define __ARMEN_H__

GeneralRe: Dialog Based on WinXP fast switch user mode Pinmembersanjay kaledhonkar4-Apr-07 21:40 
GeneralThank-you PinmemberLeo Huf6-May-06 5:10 
GeneralBug in OnAnotherInstanceMessage PinmemberVKostka22-Nov-05 2:23 
GeneralDialog based apps PinmemberMHillary18-May-04 5:30 
GeneralRe: Dialog based apps PinmemberMihai Moga17-Mar-06 2:56 
GeneralTypo in example PinmemberMealMate8-Oct-03 12:41 
GeneralRe: Typo in example Pinmemberjefflewis1-Jan-07 13:15 
GeneralI dont want to close Application on close of document. PinmemberDhirendra30-Sep-03 2:11 
GeneralAnother way of limiting to single instance PinmemberEnis18-Feb-03 9:54 
GeneralThe best way PinmemberRaffaele Romito21-May-03 1:24 
GeneralRe: The best way Pinmemberjaime_olivares10-Apr-05 21:35 
GeneralRe: The best way (variant) Pinmemberjaime_olivares10-Apr-05 21:40 
GeneralDouble click can not open file! Pinmemberxiaohui21-May-02 21:42 
GeneralRe: Double click can not open file! PinmemberArmen Hakobyan21-May-02 22:17 
GeneralRe: Double click can not open file! Pinmemberxiaohui22-May-02 0:10 
GeneralRe: Double click can not open file! PinmemberArmen Hakobyan22-May-02 2:38 
GeneralRe: Double click can not open file! PinmemberPJ Arends22-May-02 7:47 
GeneralRe: Double click can not open file! PinmemberArmen Hakobyan24-May-02 3:17 
GeneralRe: Double click can not open file! PinmemberDhirendra24-Sep-03 21:11 
GeneralRe: Double click can not open file! PinmemberDhirendra24-Sep-03 21:43 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140721.1 | Last Updated 31 Aug 2002
Article Copyright 2002 by Armen Hakobyan
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid