最近在研究这个问题,想限制只有一个应用程序实例。不过这个问题已经是老生常谈的话题了,去网上搜索一下,一大堆的结果。通常的做法都是通过 Mutex 或者 FindWindow 来进行,可是并不符合本文的需求。因为我要达到的要求是:
1、首先肯定只允许一个应用程序实例运行;
2、当第二个实例运行时,激活第一个实例,并可将相应的参数(比如需要处理的新文件名)传递给第一个实例;
3、鉴于 FrameWnd 和 Dialog 基类的处理方式(Dialog 的 DoModal() 会导致 padding)不一样,要求可以通用;
4、代码简洁高效,不想把这一个功能分散到 App 类和 Frame/Dialog 类,一个地方能搞定的就一个地方搞定。
常规的做法,比如 Mutex,可以达到第一点,但是无法实现第 2 点;用 FindWindow 的做法,完全没有考虑到窗口标题的变化(比如单文档或多文档,程序标题常变化),根本不可能做到通用;好不容易在 CodeProject 上找到一篇很有价值的文章,PJ Naughter 写的,整体是很不错,不过可惜的是没有达到第 4 点,完成这一个功能还要在好几个类里面加代码,似乎有些麻烦。
...
难道就没有合适的办法了吗?
有的!答案是肯定的。
其实 PJ Naughter 为我们指出了一条明路:在应用程序实例间共享数据,首选内存映射文件 MMF (Memory Mapped File),通过它建立一个指向虚拟的内存文件句柄,以便确定确定只有一个应用程序实例。
那激活第一个实例怎么办?我们只有在一个地方(InitInstance)有代码,此时在 Frame/Dialog 完全还没建立,所以要激活第一个实例,也就没有第一个实例的窗口句柄了,怎么办?
好办,咱不是还有 EnumWindows、EnumThreadWindows 么?枚举呗,在我们第二个实例运行的时候,第一个实例的主窗口句柄是肯定有了,并且有效(IsWindow),所以只要枚举获取了主窗口的句柄,剩下的激活主窗口、关闭第一个实例啥的都好办(这也是我转载上一篇文章的原因)。
还有一个大的问题,就是传参。前面说了,如果第 2 个实例启动的时候带命令行,比如文件、打印等,那我们需要把这个参数传递给第一个应用程序实例。说白了就是进程间通信的事情,文件?注册表?都不合适,我觉得,只有消息,还是 Windows 的消息机制是正统。但是常规的消息是不行的,不是空指针就是乱码,只有 WM_SETTEXT、WM_COPYDATA 最合适,而这两个中,后者还支持可以传递结构,所以选定后者作为参数传递的消息载体。
最终完成之后的调用代码:
hoho,是不是简洁多了? :)
参考文献:
CSingleInstance - Single Instance Apps
http://www.codeproject.com/KB/cpp/csingleinst.aspx
Avoiding Multiple Instances of an Application
http://www.codeproject.com/KB/cpp/avoidmultinstance.aspx