运行单一实例
确保运行一个进程实例的实现方法有很多,它可以通过扫描进程列表来实现,可以通过枚举程序窗口的方式来实现,也可以通过共享全局变量来实现。下面介绍一种使用广泛而且简单的方法,即通过创建系统命名互斥对象的方式来实现。
编码实现
新建控制台新项目,添加一个 .cpp 文件,添加如下代码:
#include <windows.h>
#include <iostream>
BOOL IsAlreadyRun()
{
HANDLE hMutex = NULL;
hMutex = ::CreateMutex(NULL, FALSE, L"ALREADYRUN_TEST");
if (hMutex)
{
if (ERROR_ALREADY_EXISTS == ::GetLastError())
{
return TRUE;
}
}
return FALSE;
}
int main()
{
if (IsAlreadyRun())
{
std::cout << "Already run..." << std::endl;
}
else
{
std::cout << "Not already run..." << std::endl;
}
system("pause");
return 0;
}
函数介绍
CreateMutex
函数
作用:创建或打开一个已命名或为命名的互斥对象。
函数声明:
HANDLE WINAPI CreateMutex(
_In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes,
_In_ BOOL bInitialOwner,
_In_opt_LPCTSTR lpName
);
参数:
lpMutexAttributes [om,optional] - 指向SECURITY_ATTRIBUTES结构的指针。如果此参数为NULL,则该句柄不能由子进程继承。
bInitialOwner [in] - 如果此值为TRUE并且调用者创建了互斥锁,则调用线程将获得互斥对象的初始所有权。否则,调用线程不会获得互斥锁的所有权。
lpName [in,optional] - 互斥对象的名称。该名称仅限于MAX_PATH字符,名称区分大小写。如果lpName为NULL,则会创建不带名称的互斥对象。
如果lpName与现有事件、信号量、等待定时器、作业或文件映射对象的名称匹配,且这些对象共享相同的名称空间,则该函数将失败,并且GetLastError函数返回ERROR_INVALID_HANDLE。
** 返回值**:
如果函数成功,则返回值是新创建的互斥对象的句柄。
如果函数失败,则返回值为NULL。要获得扩展的错误信息,请调用GetLastError。
如果互斥锁是一个已命名的互斥锁,并且该对象在此函数调用之前就存在,则返回值是现有对象的句柄,GetLastError返回ERROR_ALREADY_EXISTS。
实现原理
通常情况下,系统中的进程是相互独立的,每个进程都拥有自己的独立资源和地址空间,进程间互不影响。所以,同一个程序可以重复运行,但系统上的进程互不影响。但是,在一些特殊情况下,程序在系统上需要只保存一份进程实例,这就引出了进程互斥的问题。
微软提供了CreateMutex函数来创建或者打开一个已命名或未命名的互斥对象,程序在每次运行的时候,通过判断系统中是否存在相同命名的互斥对象来确定程序是否重复运行。
CreateMutex函数一共有3个参数,第一个参数表示互斥对象的安全设置,是一个指向SECURITY_ATTRIBUTES结构的指针,在该程序中直接设置为NULL即可。第二个参数表示线程是否获得互斥锁对象的初始所有权,在该程序中,无论该参数为TRUE还是FALSE,均不影响程序的正常运行。第三个参数表示互斥对象的名称,对于通过互斥对象来判断进程实例是否重复运行的程序来说,该参数一定要设置,而且要保证设置名称的唯一性。
程序的判断原理是通过CreateMutex函数创建一个命名的互斥对象,如果对象创建成功,而且通过调用GetLastError函数获取的返回码为ERROR_ALREADY_EXISTS,则表示该命名互斥对象存在,即程序重复运行。否则,认为程序是首次运行。
测试
直接运行上面的程序。第一次运行的时候,程序提示“Not already run…”,意思是系统中没有运行该实例。继续双击执行程序,这次程序提示“Already run…”,如下图所示,意思是系统上已经存在该实例且正在运行。所以,程序成功地判断出程序是否重复运行。
注意
在调用CreateMutex函数来创建命名的互斥对象时,注意互斥对象的名称不要与现有事件、信号量或者文件映射对象等名称相同,否则创建互斥对象会失败。
在实现过程中,特别要注意,程序一定不要调用CloseHandle函数来关闭由CreateMutex函数创建出来的互斥对象的句柄,否则会导致互斥对象判断失败。因为CloseHandle函数会关闭互斥对象的句柄,释放资源。这样,系统上便不会存在对应的命名互斥对象了,通过CreateMutex创建的命名互斥对象都是不会重复的。
提示
使用CreateMutex函数创建的互斥对象,可以通过调用CloseHandle函数来关闭互斥对象的句柄,从属于它的所有句柄都关闭后,就会删除该对象。
在线程同步操作中,ReleaseMutex函数可以释放线程对互斥对象的控制权。