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:
- 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. - Include the following header file (which contains the WTSRegisterSessionNotification function prototype) at the top of FastUserSwitching.cpp:
#include <wtsapi32.h>
- Add Wtsapi32.lib to the project's library list.
- 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);
- 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;
- 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.
- Rebuild the project.
- Run the application.
- From the Start menu, click Log Off, and then click Switch User.
- Click the current user name to revert to your previous user
session.
- Verify that you have received WTS_SESSION_LOCK and WTS_SESSION_UNLOCK notifications.
- Click OK to dismiss both message boxes.
- From the Start menu, click Log Off, and then click Switch User.
- Switch to a new user's session, and then switch back to the
original user session.
- Verify that you have received WTS_SESSION_LOCK, WTS_CONSOLE_DISCONNECT, WTS_SESSION_UNLOCK, and WTS_CONSOLE_CONNECT notifications.
- Click OK to dismiss all message boxes.
- 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).
- Edit FastUserSwitching.cpp.
- 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;
- 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();
- 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 );
}
- 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
- Build the project.
- Run the application.
- Minimize the application.
- Start another instance of the application and check that
the existing application instance is restored and brought to the
foreground.
- Repeatedly attempt to launch additional application
instances and ensure each time that the existing application instance is
brought to the foreground.
- With one instance of the application running, switch to a
new user session.
- 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.
- Click OK to dismiss the message box.
- 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