防止C++程序重复运行的几种方法

今天给自己的程序加了防止重复运行的功能.用的是创建互斥量的方法,感觉还不错.

下面的帖子转自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的引用:

引用
Use the CloseHandle function to close the handle. The system closes the handle automatically when the process terminates. The mutex object is destroyed when its last handle has been closed.

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值