进程的互斥运行:CreateMutex函数实现只运行一个程序实例
正常情况下,一个进程的运行一般是不会影响到其他正在运行的进程的。但是对于某些有特殊要求的如以独占方式使用串行口等硬件设备的程序就要求在其进程运行期间不允许其他试图使用此端口设备的程序运行的,而且此类程序通常也不允许运行同一个程序的多个实例。这就引出了进程互斥的问题。
实现进程互斥的核心思想比较简单:进程在启动时首先检查当前系统是否已经存在有此进程的实例,如果没有,进程将成功创建并设置标识实例已经存在的标记。此后再创建进程时将会通过该标记而知晓其实例已经存在,从而保证进程在系统中只能存在一个实例。具体可以采取内存映射文件、有名事件量、有名互斥量以及全局共享变量等多种方法来实现。下面就分别对其中具有代表性的有名互斥量和全局共享变量这两种方法进行介绍:
// 创建互斥量
HANDLE m_hMutex = CreateMutex(NULL, FALSE,"Sample07");
// 检查错误代码
// 如果程序已经存在并且正在运行
if (GetLastError() ==ERROR_ALREADY_EXISTS)
{
// 如果已有互斥量存在则释放句柄并复位互斥量
CloseHandle(m_hMutex);
m_hMutex = NULL;
// 程序退出
return FALSE;
}
上面这段代码演示了有名互斥量在进程互斥中的用法。代码的核心是CreateMutex()对有名互斥量的创建。CreateMutex()函数可用来创建一个有名或无名的互斥量对象,其函数原型为:
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes, //指向安全属性的指针
BOOL bInitialOwner, // 初始化互斥对象的所有者
LPCTSTR lpName // 指向互斥对象名的指针
);
如果函数成功执行,将返回一个互斥量对象的句柄。如果在CreateMutex()执行前已经存在有相同名字的互斥量,函数将返回这个已经存在互斥量的句柄,并且可以通过GetLastError()得到错误代码ERROR_ALREADY_EXIST。可见,通过对错误代码ERROR_ALREADY_EXIST的检测可以实现CreateMutex()对进程的互斥。
建立互斥体,用来同步。如果一个线程获取了互斥体,则要获取该互斥体的第二个线程将被挂起,直到第一个线程释放该互斥体。
参数
lpMutexAttributes
指向一个SECURITY_ATTRIBUTES结构的指针,这个结构决定互斥体句柄是否被子进程继承。
bInitialOwner
布尔类型,决定互斥体的创建者是否为拥有者
lpName
指向互斥体名字字符串的指针。互斥体可以有名字。
互斥体的好处是可以在进程间共享
将 CreateMutex 代码加到 ****App::InitInstance() 函数中即可实现只运行一个实例的效果。
以上文章转载自:我的程序员生涯 http://steveq.blog.sohu.com/71112121.html
通过上面文章我们知道:使用CreateMutex可以防止一个实例多次运行。
下面介绍如何实现当再次运行程序时如何激活已经运行的程序,比如此程序只是已被最小化。
方法如下:
BOOL C**App::InitInstance() { //创建进程互斥体Sample07 m_hMutex =CreateMutex(NULL,TRUE,_T("Sample07")); if (m_hMutex == NULL) { return FALSE; } //如果程序已经存在并且正在运行 if (GetLastError() == ERROR_ALREADY_EXISTS) { HWND hProgramWnd =::FindWindow(NULL,L"Sample07"); if (hProgramWnd) { WINDOWPLACEMENT*pWndpl = NULL; WINDOWPLACEMENT wpm; pWndpl=&wpm; GetWindowPlacement(hProgramWnd,&wpm); if(pWndpl) { //将运行的程序窗口还原成正常状态 pWndpl->showCmd= SW_SHOWNORMAL; ::SetWindowPlacement(hProgramWnd,pWndpl); SetWindowPos(hProgramWnd,HWND_NOTOPMOST,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE); } } //关闭进程互斥体 CloseHandle(m_hMutex); m_hMutex = NULL; return FALSE; } }
另一个难点是获取第一个实例的主窗对象指针或句柄,然后便可用SetForegroundWindow来激活。虽然FindWindow函数能寻找正运行着的窗口,但该函数要求指明所寻找窗口的标题或窗口类名,不是实现通用方法的途径。我们可以用Win32SDK函数SetProp来给应用程序主窗设置一个特有的标记。用GetDesktopWindow可以获取Windows系统主控窗口对象指针或句柄,所有应用程序主窗都可看成该窗口的子窗口,即可用GetWindow函数来获得它们的对象指针或句柄。用Win32SDK函数GetProp查找每一应用程序主窗是否包含有我们设置的特定标记便可确定它是否我们要寻找的第一个实例主窗。使第二个实例退出很简单,只要让其应用程序对象的InitInstance函数返回FALSE即可。此外,当主窗口退出时,应用RemoveProp函数删除我们为其设置的标记。
int CEllipseWndDlg::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CDialog::OnCreate(lpCreateStruct) == -1) return -1; // 设置寻找标记 ::SetProp(m_hWnd, AfxGetApp()->m_pszExeName, (HANDLE)1); return 0; } void CEllipseWndDlg::OnDestroy() { CDialog::OnDestroy(); // 删除寻找标记 ::RemoveProp(m_hWnd, AfxGetApp()->m_pszExeName); }
于是就可以在InitInstance()中添加代码。
// 寻找先前实例的主窗口 HWND hWndPrevious = ::GetWindow(::GetDesktopWindow(), GW_CHILD); while (::IsWindow(hWndPrevious)) { // 检查窗口是否有预设的标记? // 有,则是我们寻找的主窗 if (::GetProp(hWndPrevious, m_pszExeName)) { // 主窗口已最小化,则恢复其大小 if (::IsIconic(hWndPrevious)) ::ShowWindow(hWndPrevious, SW_RESTORE); // 将主窗激活 ::SetForegroundWindow(hWndPrevious); // 将主窗的对话框激活 ::SetForegroundWindow(::GetLastActivePopup(hWndPrevious)); // 退出本实例 return FALSE; } // 继续寻找下一个窗口 hWndPrevious = ::GetWindow(hWndPrevious, GW_HWNDNEXT); }转载自:
http://blog.sina.com.cn/s/blog_613bf01c0100eld8.html
http://blog.sina.com.cn/s/blog_4b44e1c00100bh69.html