今天给自己的程序加了防止重复运行的功能.用的是创建互斥量的方法,感觉还不错.
下面的帖子转自http://bbs.cfan.com.cn/viewthread.php?tid=793295
有时候,为了某些要求,我们希望程序实例只运行一次。而在VB6中,我们可以很轻易的根据App.hPreInstance来判断程序是否已经运行。但是在C++中,这一切就变得不是那么容易。
虽然WinMain函数有hPreInstance参数来指示,但是那是在Win16位的前提下,到了32Bit时代,那个参数已经完全成为摆设。
而本文正好探讨了如何防止C++程序重复运行的方法。
PS:因为本人使用MFC,所以为了方便,所有代码均为以MFC为基础。大家可以根据自己需要更改
1.查找窗体
对于存在GUI窗体(CUI暂不讨论)的程序来说,最容易想到的就是利用FindWindow,以标题作为参数进行查找主窗体,然后使其关闭即可。
通常,我们能写出如下代码:
// Find Window by Caption
// Add this code in InitInstance function of class
// you have derived from the CWinApp class
HWND hWnd = ::FindWindow(NULL, " MFCDialog " );
if (hWnd)
{
AfxMessageBox( " Has been running " );
return FALSE;
}
以上的代码可以简单的起到防止重复启动的效果,但是局限性很大。
首先,由于在FindWindow中要指定窗体的标题,如果窗体的标题在程序运行中是不断变化的,那么就给搜索带来了一定难度。
而且,如果其他程序也恰好是用相同的标题的话……- -#。当然,你可以通过在FindWindow中指定类名来减少错误。但是如果你看过我前面写的文章的话,你就会发现,MFC注册窗口类并不是那么随意,而是经过N次阴谋筹划之后……
看来这方法的局限性的确很大- -#
2.额外窗体存储
此方法来源于对上面一种方法的补充,因为通过搜索MFC的窗体类比较困难,而且准确度不一定高。所以,我想到了使用额外窗体存储(Extra Widnow Memory)的方法
PS:关于什么是额外窗体存储,请自行google或MSDN或查看我曾经写的The Analyses Of Windows Runnning Principle
如果你使用SDK进行开发,可以在创建窗体时填充这一属性,然后用GetWindowLong获取。
而由于我使用MFC,所以我更关注如何在MFC中使用这一属性。
一般来说,我们可以使用SetWindowLong对额外窗体存储进行填充,然后用GetWindowLong获取,最后配合FindWindow来检验程序是否重复运行。
// Add this code in InitDialog function
// and you can specify any number you want
BOOL bRet = ::SetWindowLong(GetSafeHwnd(), GWL_USERDATA, 256 );
// Add this code in InitInstance function
// Find Window by using extra memory
HWND hWnd = FindWindow(NULL, " MFCDialog " );
if (hWnd)
{
BOOL bRet = ::GetWindowLong(hWnd, GWL_USERDATA);
if ( 256 == bRet) // compare
{
AfxMessageBox( " Has been running " );
return FALSE;
}
}
3.全局原子
你可以使用GlobalAddAtom将某个特定的字符串添加到全局原子列表(Global Atom Table),然后在程序运行时检查该字符串即可。
但是这个方法有一个致命的弱点,程序退出时,Windows不会自动为你删除添加到列表中的Atom,而是需要你自己使用GlobalDeleteAtom进行删除。
这就意味着,如果你的程序意外的退出了,没有删除添加的Atom,那么,你的程序将无法运行。
所以,这并不是一个好方法。
4.枚举进程
这或许是一个毕竟正常,或者说相对稳定的方法。
我们可以使用CreateToolhelp32Snapshot或者EnumProcess来枚举当前的进程,然后检查是否已经运行。如果担心存在同名的进程,还可以检查路径。
枚举进程的代码我之前已经写过,在这里就不再重复了。
PS:在Vista下使用EnumProcess时,要注意权限问题,OpenProcess增加了一个新的权限常数,仅限Vista。如果不增加这个参数,很多进程是无法被枚举出来的(不过MS不印象我们自己的进程- -#)。
5.互斥对象
使用互斥对象来防止程序重复运行是一个很常用的做法,而且M$也推荐使用这种方法。和上面的几种方法相比,需要写的代码少,而且效率比较高。所谓方便易用~
一般我们会使用CreateMutex来创建互斥体,当第二次创建相同的互斥体时,这个API会返回前一个互斥体的Handle,而GetLastError则会返回ERROR_ALREADY_EXITS
// Mutex Object
// Add this code in InistInstance function
HANDLE hMutex = NULL;
TCHAR * lpszName = " TestMutex " ;
hMutex = ::CreateMutex(NULL, FALSE, lpszName);
DWORD dwRet = ::GetLastError();
if (hMutex)
{
if (ERROR_ALREADY_EXISTS == dwRet)
{
AfxMessageBox( " Has been running " );
CloseHandle(hMutex); // should be closed
return FALSE;
}
}
else
{
AfxMessageBox( " Create Mutex Error " );
}
// Add this code in Destruction function
::CloseHandle(hMutex);
使用互斥体时要注意几个问题:
在CreateMutex之后马上GetLastError,GetLastError是一个很复杂的API,任何牵涉到GetLastError的操作在执行之后,都会覆盖先前的值。
把正常的CloseHandle写到窗体的析构函数或者程序对象的析构函数里。不要在CreateMutex之后立刻CloseHandle,否则互斥对象会被清空。
这也是我当初所犯的错误,(不知道网上那么多错误的代码是不是经过Debug的囧),当互斥对象的最后一个Handle被Close之后,互斥对象将被删除。如果程序在退出时没有清空互斥对象,Windows将会执行这一操作。当然,把次操作交给OS不是一个好习惯。
详情请看MSDN的引用: