在Windows下,有些程序是需要单例运行的,比如QQ和Wechat吧。
- QQ可以打开多个
- Wechat只能打开一个
可以自己在Windows下点一点就知道了。
那么在Windows下如何实现单例运行呢?这个单例运行和代码的单例设计模式是不是一个东西呢?一一来回答。
首先单例模式和这个肯定是不一样的,单例设计模式解决的是在一个进程中只能有一个类实例问题;而程序单例运行是值系统中只能运行该程序的一条进程。两个单例的格局就不一样。
那代码中的单例模式是如何搞的呢?设置一个变量,这个变量全局只有一个,然后判断这个标志变量就好了。当然,单例模式还有其余更优化的做法,这里只是举一个例子。就像你想保护一段互斥区,你会加一把锁一样。
Windows下的程序单例运行就是通过这种全局变量的方法来做的。
Windows的事件对象和互斥锁是全局唯一的,我们可以考虑从这两点入手。
CreateEvent
CreatMutex
这两个函数分别可以创建一个全局(整个系统)唯一的事件对象和互斥锁,如果重复创建,那么就会创建失败。这正是我们实现单例运行的原理。
这两个API的用法可以查阅MSDN,这里多说一下Windows系统的Event对象,这个Event对象和C++中的事件Event(基于条件变量的封装)中的Event很相似,只不过Windows系统的下的Event是整个系统唯一的,而我自己封装的Event只适合在一个进程中传递信号,且Event的个数是不限的。
那么单例运行的核心代码如下:
名字前面加Global
表示这个事件对象或者互斥锁是全局的,这是Windows的名字空间。这样事件对象和互斥锁就可以跨Session检测了。
std::wstring singleName = L"Global\\" + exeName;
singleEvent_ = Function(NULL, FALSE, FALSE, singleName.c_str());
// 这里Function可以是CreatEvent和CreateMutex
if (!singleEvent_ || ::GetLastError() == ERROR_ALREADY_EXISTS) OnAlreadyExistExe();
既然Function两个都可以,那我们选哪个?如果你只是想要实现单例运行,那么两个随便选;如果你还需要多一点的信息和需求,那么要选CreateEvent
。原因如下:
现在有一个需求,如果我这个程序在运行的过程中又有一个相同的程序起来了,运行中的这个程序要知道,且做出一些反应。这个时候创建互斥锁CreateMutex
就做不到了,因为Mutex不能通信,而Event有信号,它可以通信。可以通信就可以实现这个需求。
下面开始实现单例运行,把单例运行封装到一个类中,让这个类的生命周期和整个程序的生命周期一样,这样子就实现了单例运行,我们实习那一个SingletonExe
类。
class SingletonExe
{
public:
~SingletonExe();
void Init(std::wstring exeName);
SingletonExe & SetCBOfAlreadyExistExe(std::function<void(void)> cb);
SingletonExe & SetCBOfOtherRunExe(std::function<void(void)> cb);
private:
void OnAlreadyExistExe();
void CheckOtherExeRun();
private:
std::thread thread_;
std::function<void(void)> alreadyExistExeCB_;
std::function<void(void)> otherRunExeCB_;
HANDLE singleEvent_;
HANDLE closeEvent_;
};
设置两个回调函数alreadyExistExeCB_
和otherRunExeCB_
,分别表示 [ 如果当前系统已经有一个实例在运行那么就会调 ] 和 [ 如果当前进程已经在运行,这个时候又有新的实例运行,这时当前进程回调 ] ,其实这两个回调都发生在有多个实例运行时,只不过回调这两个函数的对象不同而已。
为了实现当前实例已经运行,同时还要监视其他实例有没有运行,这里需要再开一条线程去监视,同时还需要创建一个事件对象closeEvent_
,这个事件对象用于这两条线程之间的通信,这里可以用C++中的事件Event(基于条件变量的封装)中的Event。
下面给出各个成员函数的实现,其中系统API的传参需要对这些API的参数有了解才能知道为什么传这些参数。
SingletonExe::~SingletonExe()
{
if (closeEvent_) SetEvent(closeEvent_); // 发关闭信号
if (thread_.joinable()) thread_.join();
}
void SingletonExe::Init(std::wstring exeName)
{
assert(!exeName.empty());
std::wstring singleName = L"Global\\" + exeName; // 全局唯一
singleEvent_ = ::CreateEvent(NULL, FALSE, FALSE, singleName.c_str());
if (!singleEvent_ || ::GetLastError() == ERROR_ALREADY_EXISTS) OnAlreadyExistExe();
if (otherRunExeCB_)
{
closeEvent_ = ::CreateEvent(NULL, TRUE, FALSE, NULL);
thread_ = std::thread(&SingletonExe::CheckOtherExeRun, this);
}
}
SingletonExe & SingletonExe::SetCBOfAlreadyExistExe(std::function<void(void)> cb)
{
alreadyExistExeCB_ = cb;
return *this;
}
SingletonExe & SingletonExe::SetCBOfOtherRunExe(std::function<void(void)> cb)
{
otherRunExeCB_ = cb;
return *this;
}
#define SINGLE_EXE 1314U
void SingletonExe::OnAlreadyExistExe()
{
if (alreadyExistExeCB_) alreadyExistExeCB_();
::SetEvent(singleEvent_); // 发互斥信号
exit(SINGLE_EXE);
}
void SingletonExe::CheckOtherExeRun()
{
HANDLE handles[] = { singleEvent_, closeEvent_ };
for (; ;)
{
// 收信号
switch (WaitForMultipleObjects(sizeof(handles) / sizeof(HANDLE), handles, FALSE, INFINITE))
{
case WAIT_OBJECT_0:
otherRunExeCB_();
break;
case WAIT_OBJECT_0 + 1:
::CloseHandle(closeEvent_);
return;
default:
break;
}
}
}
正是Event的通信机制才能实现现在的这种完善的单例运行。可以看看程序中的关键注释。
实例一下如何使用吧
int main()
{
SingletonExe single;
single.SetCBOfAlreadyExistExe([]
{
std::cout << "has a exe running!" << std::endl;
}).SetCBOfOtherRunExe([]
{
std::cout << "has other exe want to run" << std::endl;
}).Init(L"FlushHip");
std::this_thread::sleep_for(std::chrono::hours(1));
return 0;
}
在VS2015中分别验证回调函数alreadyExistExeCB_
和otherRunExeCB_
是否都调用了。
- 先在VS中运行一个实例,然后打开项目目录中的Dubug目录,再运行一个实例,可以看到第二次运行的实例一闪而过,说明单例成功;同时第一个实例的窗口中打出了
has other exe want to run
,说明otherRunExeCB_
被回调了。 - 先点开项目目录中的Dubug目录,运行一个实例,然后在VS中下一个断点,断在
exit(SINGLE_EXE);
处,然后调试,这时VS中的窗口会打出has a exe running!
,说明alreadyExistExeCB_
回调了;在VS中点继续,窗口消失,说明单例成功。
有时间再弄下Linux下C++程序实现单例运行吧,原理是一样的,有差别的是API。