概要

概要

快速用户切换是 Windows XP 的一个功能,允许多个用户共享同一台计算机。 每个用户有自己的配置文件(和桌面),而且您可以在不注销的情况下在用户之间进行切换。 您编写的应用程序若要支持快速用户切换,必须确保在用户会话切换时不损坏或丢失数据。

若要支持快速用户切换,您的应用程序必须将用户和应用程序数据存储在有效的位置。

此外,如果您的应用程序提供的功能在多个用户同时运行时发生故障(例如,因为该程序以不安全的方式使用全局资源),您必须为应用程序添加代码以检测该情形并作出相应的反应。

如果应用程序的另一个实例影响到可选(非首要)功能,应用程序启动时必须:
检测是否有用户正在运行该应用程序。
阻止所有有问题的功能。
通知当前用户无法使用特定功能的原因。
如果应用程序的另一个实例影响首要功能,同样,您的应用程序必须:
检测是否有用户正在运行该应用程序。
向当前用户报告错误情况,然后退出。
最后,如果您的应用程序需要知道何时要在活动用户会话中运行以及何时发生了会话切换,那么该应用程序可以进行注册以接收会话通知消息。 例如,一个监视设备是否连接到串行端口的应用程序需要在结束活动用户会话运行状态时释放端口,而在其再次进入活动会话状态时重新获得该端口。 此外,在非活动用户会话状态下运行应用程序时挂起后台处理,可以节省系统资源。

本文其他部分指导您如何使您的应用程序支持快速用户切换。


回到顶端

要求

下表概括了推荐使用的硬件、软件、网络架构以及所需的 Service Pack:

Windows XP Home Edition 或 Windows XP Professional Edition。
Microsoft Visual Studio .NET 或 Visual Studio 6.0。
Microsoft Platform SDK June 2001 版本或更新版中的头文件和库。
必需的预备知识:
Win32 应用程序开发。
熟悉 Windows XP 快速用户切换。

回到顶端

创建 Win32 应用程序

启动 Visual Studio 并新建一个名为 FastUserSwitching 的 Win32 应用程序。
Visual C++ 6.0 用户: 从可用项目类型列表中选择 Win32 应用程序,然后在应用程序安装向导中选择一个典型的“Hello World”应用程序
Visual Studio .NET 用户: 在 Visual C++ 项目中选择 Win32 项目并接受应用程序安装向导中显示的默认应用程序设置。

回到顶端

添加接收会话切换通知的代码

如果您的应用程序需要知道何时要在活动用户会话中运行以及何时发生了会话切换,该应用程序可以通过调用 WTSRegisterSessionNotification 函数进行注册以接收 WM_WTSSESSION_CHANGE 消息:
1. 打开 stdafx.h 并在包含 windows.h 的语句之前添加以下 #define 语句:
#define _WIN32_WINNT 0x0501
这是 winuser.h 的要求,其目的是定义通知类型和宏。
2. FastUserSwitching.cpp 的顶部包含以下头文件(其中包含 WTSRegisterSessionNotification 函数原型):
#include <wtsapi32.h>
3. 将 Wtsapi32.lib 添加到项目的库列表。
4. 在 FastUserSwitching.cpp 中找到 InitInstance 函数。在函数的尾部的 return 语句之前,添加对 WTSRegisterSessionNotification 的调用,如下所示:
WTSRegisterSessionNotification(hWnd, NOTIFY_FOR_THIS_SESSION);
5. 找到 WndProc 窗口过程并添加处理 WM_WTSSESSION_CHANGE 消息的 case 语句。 此消息的 wParam 包含状态编码,表明发出会话更改通知的原因。 添加以下代码检测可用状态编码的子集并显示消息框,表明已收到哪些状态编码:
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. 每一个对 WTSRegisterSessionNotification 的调用应与一个对 WTSUnRegisterSessionNotification 的调用匹配。 在 WndProc 中修改 WM_DESTROY 消息的处理,如下所示:
case WM_DESTROY:
            WTSUnRegisterSessionNotification(hWnd);
            PostQuitMessage(0);
            break;

回到顶端

确认会话切换通知

本任务假定您至少有两个 Windows XP 用户帐户。 如果您只有一个帐户,请再新建一个帐户。
1. 重新生成项目。
2. 运行该应用程序。
3. 开始菜单中,单击注销,然后单击切换用户
4. 单击当前用户名返回到前一个用户会话。
5. 确认您已经收到 WTS_SESSION_LOCK WTS_SESSION_UNLOCK 通知。
6. 单击确定可消除这两个消息框。
7. 开始菜单中,单击注销,然后单击切换用户
8. 切换到新用户会话,然后再切换回原来的用户会话。
9. 确认您已经收到 WTS_SESSION_LOCKWTS_CONSOLE_DISCONNECTWTS_SESSION_UNLOCK WTS_CONSOLE_CONNECT 通知。
10. 单击确定可消除所有消息框。
11. 关闭应用程序。

回到顶端

检测现有应用程序实例

您的应用程序可能需要检测正在运行的实例,目的是:
禁用某些无法在多个实例间共享的功能。
防止后续实例运行(仅在极端情况下)。
注意,只要可能,您必须确保 Windows XP 应用程序支持在同用户会话或不同用户会话环境下同时运行多个实例。 不支持多实例的应用程序被认为是欠佳的 Windows XP 应用程序。

若要检测现有的应用程序实例,使用一个全局 mutex 或 semaphore 对象(名称已知)。 在对象名前添加前缀“Global\”确保使用全局命名空间。 这样您就可以检测在不同用户会话环境中运行的您的应用程序实例。

使用 FindWindow FindWindowEx 的传统方法在启用快速用户切换的 Windows XP 系统中不起作用,因为这些方法不会检测在不同用户会话环境中(或不同桌面)运行的应用程序实例。
1. 编辑 FastUserSwitching.cpp
2. 在文件顶部的现有全局变量后声明并初始化一个全局变量,存储 mutex 对象的句柄。
HANDLE g_hMutexAppRunning = NULL;
3. 为新建函数添加以下函数原型,检测应用程序实例是否已存在:
BOOL AppInstanceExists();
4. 在源文件的末尾,使用以下代码创建 AppInstanceExists 函数。 此代码试图创建一个全局 mutex 对象,然后检查是否创建并打开了 mutex 对象(通过检查错误代码 ERROR_ALREADY_EXISTS 实现)。 在这种情况下,错误代码表明已有应用程序实例运行。 如果是这样,代码关闭 mutex 对象并返回“TRUE”。 如果此函数成功创建了一个新的 mutex 对象,将返回“FALSE”,表明这是第一个应用程序实例。
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. 您必须确保当运行的应用程序终止时,mutex 对象关闭。 将以下代码添加到 WinMain 函数的末尾,位于消息循环之后,最后的 return 语句之前:
if (g_hMutexAppRunning != NULL )
            {
            CloseHandle(g_hMutexAppRunning);
            g_hMutexAppRunning = NULL;
            }

回到顶端

将现有应用程序实例设置到前台

如果只允许运行应用程序的一个实例,您应当使用 FindWindow SetForegroundWindow API 在后续实例启动时将现有实例置于前台(如果现有实例运行在当前用户会话中)。 您必须测试 FindWindow 的返回值,因为如果现有应用程序实例在另一个用户的会话中运行,将返回 NULL。

找到 InitInstance 函数进行修改,如下所示:
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;
}

回到顶端

测试应用程序检测

1. 生成项目。
2. 运行该应用程序。
3. 最小化应用程序。
4. 启动应用程序的另一个实例,检查现有应用程序是否被恢复并置于前台。
5. 反复启动其他的启动应用程序实例,并确保每次启动时现有应用程序都置于前台。
6. 在应用程序的一个实例运行时,切换到新的用户会话。
7. 试图启动应用程序,您会看到一个消息框,它说明了该应用程序已在另一个用户会话中运行。
8. 单击确定消除此消息框。
9. 返回到原来的用户会话,关闭会话切换通知消息窗口并退出应用程序。

回到顶端

疑难解答

如果您的应用程序在 Windows XP 或 Windows 2000 中运行,您只能命名使用全局命名空间的 mutex 和 semaphore 对象。早期版本的 Windows 不支持全局命名空间,而且如果试图使用包含反斜杠字符 ("\") 的内核对象名称将发生错误。
如果您的应用程序是针对 Windows XP 或 Windows 2000 及其他版本的 Windows 设计的,则必须包含版本检查代码,并且在相应的操作系统中只能使用全局 mutex 名称。
如果您在开发一项服务,请确保服务提供的用户交互发生在当前用户上。 不可假定会话 0 就是当前桌面会话,因为在 Windows XP 中,活动会话可以有任意的会话序号。 使用 WTSGetActiveConsoleSessionID 标识活动会话。