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

 

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

有时候,为了某些要求,我们希望程序实例只运行一次。而在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.

你可能感兴趣的:(防止C++程序重复运行的几种方法)