如果应用程序的另一个实例影响到可选(非首要)功能,应用程序启动时必须:
1)检测是否有用户正在运行该应用程序。
2)阻止所有有问题的功能。
3)通知当前用户无法使用特定功能的原因。
如果应用程序的另一个实例影响首要功能,同样,您的应用程序必须:
1)检测是否有用户正在运行该应用程序。
2)向当前用户报告错误情况,然后退出。
下面给出一个实例:
创建 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 包含状态编码,表明发出会话更改通知的原因。 添加以下代码检测可用状态编码的子集并显示消息框,表明已收到哪些状态编码:
code
1case WM_WTSSESSION_CHANGE:
2 switch( wParam )
3 {
4 case WTS_CONSOLE_CONNECT:
5 MessageBox(hWnd, TEXT("WTS_CONSOLE_CONNECT"),
6 TEXT("WM_WTSSESSION_CHANGE"), MB_OK );
7 break;
8 case WTS_CONSOLE_DISCONNECT:
9 MessageBox(hWnd, TEXT("WTS_CONSOLE_DISCONNECT"),
10 TEXT("WM_WTSSESSION_CHANGE"), MB_OK );
11 break;
12 case WTS_SESSION_LOCK:
13 MessageBox(hWnd, TEXT("WTS_SESSION_LOCK"),
14 TEXT("WM_WTSSESSION_CHANGE"), MB_OK );
15 break;
16 case WTS_SESSION_UNLOCK:
17 MessageBox(hWnd, TEXT("WTS_SESSION_UNLOCK"),
18 TEXT("WM_WTSSESSION_CHANGE"), MB_OK );
19 break;
20 default:
21 break;
22 }
23break;
24
6、每一个对 WTSRegisterSessionNotification 的调用应与一个对 WTSUnRegisterSessionNotification 的调用匹配。 在 WndProc 中修改 WM_DESTROY 消息的处理,如下所示:
case WM_DESTROY:
WTSUnRegisterSessionNotification(hWnd);
PostQuitMessage(0);
break;
确认会话切换通知
本任务假定至少有两个用户帐户。 如果您只有一个帐户,请再新建一个帐户。
1、重新生成项目。
2、运行该应用程序。
3、从开始菜单中,单击注销,然后单击切换用户。
4、单击当前用户名返回到前一个用户会话。
5、确认您已经收到 WTS_SESSION_LOCK 和 WTS_SESSION_UNLOCK 通知。
6、单击确定可消除这两个消息框。
7、从开始菜单中,单击注销,然后单击切换用户。
8、切换到新用户会话,然后再切换回原来的用户会话。
9、确认您已经收到 WTS_SESSION_LOCK、WTS_CONSOLE_DISCONNECT、WTS_SESSION_UNLOCK 和 WTS_CONSOLE_CONNECT 通知。
10、单击确定可消除所有消息框。
11、关闭应用程序。
检测现有应用程序实例
若要检测现有的应用程序实例,使用一个全局 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”,表明这是第一个应用程序实例。
code
1BOOL AppInstanceExists()
2{
3 BOOL bAppRunning = FALSE;
4 // Create a global mutex. Use a unique name, for example
5 // incorporating your company and application name.
6 g_hMutexAppRunning = CreateMutex( NULL, FALSE, "Global\\My MpApp.EXE");
7 // Check if the mutex object already exists, indicating an
8 // existing application instance
9 if (( g_hMutexAppRunning != NULL ) &&
10 ( GetLastError() == ERROR_ALREADY_EXISTS))
11 {
12 // Close the mutex for this application instance. This assumes
13 // the application will inform the user that it is
14 // about to terminate
15 CloseHandle( g_hMutexAppRunning );
16 g_hMutexAppRunning = NULL;
17 }
18 // Return False if a new mutex was created,
19 // as this means it's the first app instance
20 return ( g_hMutexAppRunning == NULL );
21}
5、您必须确保当运行的应用程序终止时,mutex 对象关闭。 将以下代码添加到 WinMain 函数的末尾,位于消息循环之后,最后的 return 语句之前:
if (g_hMutexAppRunning != NULL )
{
CloseHandle(g_hMutexAppRunning);
g_hMutexAppRunning = NULL;
}
将现有应用程序实例设置到前台
如果只允许运行应用程序的一个实例,您应当使用 FindWindow 和 SetForegroundWindow API 在后续实例启动时将现有实例置于前台(如果现有实例运行在当前用户会话中)。 您必须测试 FindWindow 的返回值,因为如果现有应用程序实例在另一个用户的会话中运行,将返回 NULL。
找到 InitInstance 函数进行修改,如下所示:
code
1 BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
2 {
3 HWND hWnd;
4 hInst = hInstance;
5 // Check if another application instance is already running
6 if ( AppInstanceExists() == TRUE )
7 {
8 HWND hWndOtherInstance;
9 hWndOtherInstance = FindWindow(szWindowClass, szTitle);
10 if ( hWndOtherInstance != (HWND)NULL )
11 {
12 // Application is running in current user's session
13 if ( IsIconic(hWndOtherInstance) )
14 ShowWindow(hWndOtherInstance, SW_RESTORE);
15 SetForegroundWindow(hWndOtherInstance);
16 }
17 else
18 {
19 MessageBox(NULL, TEXT("An instance of this app is running in another user's session"), szTitle, MB_OK);
20 }
21 return FALSE;
22 }
23 hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
24 CW_USEDEFAULT, , CW_USEDEFAULT, , NULL, NULL,
25 hInstance, NULL );
26 if (!hWnd)
27 return FALSE;
28
29 ShowWindow(hWnd, nCmdShow);
30 UpdateWindow(hWnd);
31 WTSRegisterSessionNotification(hWnd, NOTIFY_FOR_THIS_SESSION);
32 return TRUE;
33}
测试应用程序检测
1、生成项目。
2、运行该应用程序。
3、最小化应用程序。
4、启动应用程序的另一个实例,检查现有应用程序是否被恢复并置于前台。
5、反复启动其他的启动应用程序实例,并确保每次启动时现有应用程序都置于前台。
6、在应用程序的一个实例运行时,切换到新的用户会话。
7、试图启动应用程序,您会看到一个消息框,它说明了该应用程序已在另一个用户会话中运行。
8、单击确定消除此消息框。
9、返回到原来的用户会话,关闭会话切换通知消息窗口并退出应用程序