让程序只运行一个实例的方法有数种,但原理都是相同的,就是在程序的主窗口创建之前,检查系统中是否已经存在某个与本程序相关的特定标志了。如果存在,则说明已经有一个实例在运行了,当前程序不用创建主窗口,直接退出即可。否则,就说明本程序是第一次运行。各种方法所不同的是,各自检查的标志不同,这也使得各种方法在使用时各有利弊了。
一般来说,使程序只运行一个实例的最简单的方法当然是使用FindWindow()查找主窗口,如果主窗口已经存在了,当然说明已经有一个实例运行了。代码如下:
// 主窗口创建前
HWND hWnd = FindWindow("ClassName", "Caption");
if(IsWindow(hWnd))
{
ShowWindow(hWnd, SW_NORMAL); // 显示
SetForegroundWindow(hWnd); // 激活
return;
}
这个方法的不足之处是,FindWindow()的参数ClassName和Caption比较难取得。比如,凡是使用DialogBoxParam()创建的对话框,他们的ClassName都是“#32770”,没有唯一性;而使用MFC创建的Doc/View结构的窗口的Caption更是会随Doc?Name的不同而有所变化。
另一种方法就是使用Mutex互斥体了。代码如下:
// 声明全局的局柄
HANDLE g_hHandle;
// 主窗口创建前
g_hHandle = CreateMutex(NULL, FALSE, "Mutex Name");
if(GetLastError() == ERROR_ALREADY_EXISTS)
{
return FALSE;
}
使用Mutex代码比较简洁,但是此时不能取得已经启动的实例窗口局柄,因此无法激活已经启动的实例窗口。
第三种方法是我认为比较完善的方法,就是通过SetProp()为程序主窗口设置一个特殊的Property,然后在启动时遍历所有的窗口,找出包含着个Property的窗口局柄。这个方法的缺点就是代码比较多。如下:
// 声明全局的property名和property value
LPCTSTR g_szPropName = "prop name";
HANDLE g_hValue = (HANDLE)1;
// 定义枚举窗口回调函数
BOOL CALLBACK EnumWndProc(HWND?hwnd,?LPARAM?lParam)
{
???????HANDLE?h?=?GetProp(hwnd,?g_szPropName);
???????if(?h?==?g_hValue)
???????{
?????????????*(HWND*)lParam?=?hwnd;
?????????????return?false;
???????}
???????return?true;
}
//?主窗口创建前判断
HWND?hWnd?=?NULL;
EnumWindows(EnumWndProc,?(LPARAM)&hWnd);
if(IsWindow(hWnd))
{
??????ShowWindow(hWnd,?SW_NORMAL);
??????SetForegroundWindow(hWnd);
??????return?FALSE;
}
//?主窗口创建后设置
SetProp(m_hWnd,?g_szPropName,?g_hValue);
这个方法就是需要遍历系统中所有的窗口,效率可能稍低了些。
使用全局共享变量的方法则主要是在MFC框架程序中通过编译器来实现的。通过#pragma data_seg预编译指令创建一个新节,在此节中可用volatile关键字定义一个变量,而且必须对其进行初始化。Volatile关键字指定了变量可以为外部进程访问。最后,为了使该变量能够在进程互斥过程中发挥作用,还要将其设置为共享变量,同时允许具有读、写访问权限。这可以通过#pragma comment预编译指令来通知编译器。下面给出使用了全局变量的进程互斥代码清单:
#pragma data_seg("Shared")
int volatile g_lAppInstance =0;
#pragma data_seg()
#pragma comment(linker,"/section:Shared,RWS")
……
if(++g_lAppInstance>1)
return FALSE;
此段代码的作用是在进程启动时对全局共享变量g_nAppInstancd 加1 ,如果发现其值大于1,那么就返回FALSE以通知进程结束。这里需要特别指出的是,为了使以上两段代码能够真正起到对进程互斥的作用,必须将其放置在应用程序的入口代码处,即应用程序类的初始化实例函数InitInstance()的开始处。