How To Write an Application That Supports Fast User Switching in Windows XP (310153)



The information in this article applies to:

  • Microsoft Windows XP Professional
  • Microsoft Visual C++ .NET (2002)
  • Microsoft Visual C++, 32-bit Editions 6.0
  • Microsoft Visual C++ .NET (2003)

This article was previously published under Q310153

SUMMARY

Fast user switching is a Windows XP feature that allows multiple users to share the same computer. Each user has an individual profile (and desktop), and you can switch between users without logging off. Applications that you write must support fast user switching by not crashing or losing data when a user session switch occurs.

In order to support fast user switching, your application must store user and application data in valid locations.

Additionally, if your application provides functionality that will fail in the event of multiple users running it simultaneously (for example, because it uses a global resource in an unsafe way), you must add code to your application to detect this condition and react appropriately.

If a second application instance will affect optional (nonprimary) functionality, when your application starts it must:
  • Detect that a user is already running the application.
  • Block any problematic features.
  • Inform the current user why certain functionality will be unavailable.
If a second application instance will affect primary functionality, your application must also:
  • Detect that a user is already running the application.
  • Report an error condition to the current user and exit gracefully.
Finally, if your application needs to know when it is running in the active user session and when session switches occur, it can register to receive session notification messages. For example, an application that monitors for a device being connected to a serial port may want to release the port when it is not running in the active user session and reacquire the port when its session becomes active again. Also, an application may be able to conserve system resources by suspending background processing while running in a nonactive user session.

The remainder of this article will guide you through the process of enabling your application to support fast user switching.

back to the top

Requirements

The following items describe the recommended hardware, software, network infrastructure, and service packs you will need:

  • Windows XP Home Edition or Windows XP Professional Edition.
  • Microsoft Visual Studio .NET or Visual Studio 6.0.
  • Header files and libraries from the Microsoft Platform SDK June 2001 edition or later.
Prior knowledge Required:
  • Win32 application development.
  • Familiarity with Windows XP fast user switching.
back to the top

Create a Win32 application

Start Visual Studio and create a new Win32 application called FastUserSwitching.
  • Users of Visual C++ 6.0: select Win32 Application from the list of available project types, and then select A typical "Hello World" application within the Application Setup Wizard.
  • Users of Visual Studio .NET: select Win32 Project within Visual C++ Projects and accept the default application settings displayed by the Application Setup Wizard.
back to the top

Add Code to Receive Session Switch Notifications

If your application needs to know when it is running in the active user's session and when session switches occur, it can register to receive the WM_WTSSESSION_CHANGE message by calling the WTSRegisterSessionNotification function:
  1. Open stdafx.h and add the following #define statement before the inclusion of windows.h:
    #define _WIN32_WINNT 0x0501
    						
    This is required by winuser.h in order to expose session notification types and macros.
  2. Include the following header file (which contains the WTSRegisterSessionNotification function prototype) at the top of FastUserSwitching.cpp:
    #include <wtsapi32.h>
    					
  3. Add Wtsapi32.lib to the project's library list.
  4. Locate the InitInstance function in FastUserSwitching.cpp. At the bottom of the function, just before the final return statement, add a call to WTSRegisterSessionNotification, as shown here:
    WTSRegisterSessionNotification(hWnd, NOTIFY_FOR_THIS_SESSION);
  5. Locate the WndProc window procedure and add a case statement to process WM_WTSSESSION_CHANGE messages. This message's wParam contains a status code that indicates the reason that the session change notification was sent. Add the following code to detect a subset of the available status codes and to display message boxes to indicate which status codes have been received:
    case WM_WTSSESSION_CHANGE:
       switch( wParam )
       {
          case WTS_CONSOLE_CONNECT:
             MessageBox(hWnd, TEXT("WTS_CONSOLE_CONNECT"), 
                        TEXT("WM_WTSSESSION_CHANGE"), MB_OK );
             break;
          case WTS_CONSOLE_DISCONNECT:
             MessageBox(hWnd, TEXT("WTS_CONSOLE_DISCONNECT"), 
                        TEXT("WM_WTSSESSION_CHANGE"), MB_OK );
             break;
          case WTS_SESSION_LOCK:
             MessageBox(hWnd, TEXT("WTS_SESSION_LOCK"), 
                        TEXT("WM_WTSSESSION_CHANGE"), MB_OK );
             break;
          case WTS_SESSION_UNLOCK:
             MessageBox(hWnd, TEXT("WTS_SESSION_UNLOCK"), 
                        TEXT("WM_WTSSESSION_CHANGE"), MB_OK );
             break;
       default:
          break;
       }
       break;
    
    					
  6. Every call to WTSRegisterSessionNotification should be matched with a call to WTSUnRegisterSessionNotification. Modify the processing of the WM_DESTROY message within WndProc, as follows:
    case WM_DESTROY:
       WTSUnRegisterSessionNotification(hWnd);
       PostQuitMessage(0);
       break;
    					
back to the top

Verify Session Switch Notifications

This task assumes that you have at least two Windows XP user accounts. If you only have one account, create a new account first.
  1. Rebuild the project.
  2. Run the application.
  3. From the Start menu, click Log Off, and then click Switch User.
  4. Click the current user name to revert to your previous user session.
  5. Verify that you have received WTS_SESSION_LOCK and WTS_SESSION_UNLOCK notifications.
  6. Click OK to dismiss both message boxes.
  7. From the Start menu, click Log Off, and then click Switch User.
  8. Switch to a new user's session, and then switch back to the original user session.
  9. Verify that you have received WTS_SESSION_LOCK, WTS_CONSOLE_DISCONNECT, WTS_SESSION_UNLOCK, and WTS_CONSOLE_CONNECT notifications.
  10. Click OK to dismiss all message boxes.
  11. Close the application.
back to the top

Detect an Existing Application Instance

Your application may need to detect existing running instances in order to:
  • Disable certain functionality that cannot be shared between multiple instances.
  • Prevent subsequent instances from running (in extreme cases only).
Note that wherever possible you must ensure that Windows XP applications support multiple instances running simultaneously in the same or different user sessions. Applications that do not allow multiple instances are not considered best-practice Windows XP applications.

To detect existing application instances, use a global mutex or semaphore object with a known name. Prefix the object's name with "Global\" to ensure that the global namespace is used. This allows you to detect instances of your applications that are running in separate user sessions.

The traditional approach of using FindWindow or FindWindowEx will not work on a Windows XP system on which fast user switching is enabled because it will not detect application instances that are running in different user sessions (on different desktops).
  1. Edit FastUserSwitching.cpp.
  2. Below the existing global variables at the top of the file, declare and initialize a global variable to store the handle of a mutex object:
    HANDLE g_hMutexAppRunning = NULL;
    					
  3. Add the following function prototype for a new function that you will create to detect whether or not an existing application instance exists:
    BOOL AppInstanceExists();
    					
  4. At the bottom of the source file, use the following code to create the AppInstanceExists function. This code attempts to create a global mutex object and then checks to see whether the mutex object was created or opened (by checking for the error code ERROR_ALREADY_EXISTS). In this scenario, the error code indicates that an existing application instance is running. In this case, the code closes the mutex object and returns "TRUE". If the function successfully creates a new mutex object, it returns "FALSE" to indicate that this is the first application instance.
    BOOL AppInstanceExists()
    {
       BOOL bAppRunning = FALSE;
       // Create a global mutex. Use a unique name, for example 
    // incorporating your company and application name.
       g_hMutexAppRunning = CreateMutex( NULL, FALSE, 
                                         "Global\\My Company MpApp.EXE");
       // Check if the mutex object already exists, indicating an
       // existing application instance
       if (( g_hMutexAppRunning != NULL ) && 
             ( GetLastError() == ERROR_ALREADY_EXISTS))
       {
          // Close the mutex for this application instance. This assumes
          // the application will inform the user that it is 
          // about to terminate 
          CloseHandle( g_hMutexAppRunning );
          g_hMutexAppRunning = NULL;
       }
       // Return False if a new mutex was created, 
    // as this means it's the first app instance
       return ( g_hMutexAppRunning == NULL );
    }
    					
  5. You must ensure that when the running application instance terminates, the mutex object is closed. Add the following code to the bottom of the WinMain function, after the message loop and just before the final return statement:
    if (g_hMutexAppRunning != NULL )
    {
      CloseHandle(g_hMutexAppRunning);
      g_hMutexAppRunning = NULL;
    }
    					
back to the top

Set an Existing Application Instance to the Foreground

If you only allow a single instance of your application, you should use the FindWindow and SetForegroundWindow APIs when a subsequent instance starts to bring the existing instance to the foreground if it is running in the current user's session. You must test the return value from FindWindow, as this will return NULL if the existing application instance is running in another user's session.

Locate the InitInstance function and modify it as shown here:
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   HWND hWnd;
   hInst = hInstance; 
   // Check if another application instance is already running
   if ( AppInstanceExists() == TRUE )
   {
      HWND hWndOtherInstance;
      hWndOtherInstance = FindWindow(szWindowClass, szTitle);
      if ( hWndOtherInstance != (HWND)NULL )
      {
         // Application is running in current user's session
         if (IsIconic(hWndOtherInstance)) 
            ShowWindow(hWndOtherInstance, SW_RESTORE);
         SetForegroundWindow(hWndOtherInstance);
      }
      else
      {
         MessageBox(NULL, TEXT(
         "An instance of this app is running in another user's session"),
         szTitle, MB_OK);
      }
      return FALSE;
   }
   hWnd =  CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL,
                          hInstance, NULL );
   if (!hWnd)
      return FALSE;

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);
   WTSRegisterSessionNotification(hWnd, NOTIFY_FOR_THIS_SESSION);
   return TRUE;
}
				
back to the top

Verify Application Detection

  1. Build the project.
  2. Run the application.
  3. Minimize the application.
  4. Start another instance of the application and check that the existing application instance is restored and brought to the foreground.
  5. Repeatedly attempt to launch additional application instances and ensure each time that the existing application instance is brought to the foreground.
  6. With one instance of the application running, switch to a new user session.
  7. Attempt to start the application and verify that the message box explaining that the application is already running in another user's session is displayed.
  8. Click OK to dismiss the message box.
  9. Revert to the original user session, dismiss the session switch notification message boxes, and close the application.
back to the top

Troubleshooting

  • You can only name mutex and semaphore objects that use the global namespace if your application is running on Windows XP or Windows 2000. Earlier versions of Windows do not support global namespaces, and an error will occur if you attempt to use a name for a kernel object that contains a backslash character ("\").
  • If your application is designed to run on Windows XP or Windows 2000 and other versions of Windows, you must incorporate version-checking code and only use a global mutex name on the appropriate operating systems.
  • If you are developing a service, make sure that any user interactions from the service occur with the correct user. Do not assume that session 0 is the current desktop session, because on Windows XP the active session can have any session number. Use WTSGetActiveConsoleSessionID to identify the active session.
back to the top

Modification Type:MinorLast Reviewed:7/15/2004
Keywords:kbHOWTOmaster KB310153 kbAudDeveloper