Windows下C++程序实现单例运行

版权声明:本文为博主原创文章,禁止转载。 https://blog.csdn.net/FlushHip/article/details/85054118

在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。

猜你喜欢

转载自blog.csdn.net/FlushHip/article/details/85054118