1/22/2005

限制应用程序的实例数目

某些应用程序处理紧缺资源,例如可擦写光驱、串口或者大量内存,通常不希望这种应用程序的多个实例同时运行。

实际上你没有办法限制用户只能启动一次。你可以做到的是在应用程序启动之后查找是否用户启动了另一个实例。如果没有找到现存的实例,应用程序以正常方式启动。否则,通常的处理是退出。

要查找现存的实例,可以查找进程列表,比较每一个进程的文件名称和本应用程序的文件名称,如果找到了和文件名匹配的进程,那么认为应用程序已经启动了(参考平台SDK的Spy示例和Visual C++的TList示例)。这种方法的缺点是需要额外的工作(例如枚举找到的进程的窗口)来传递命令行参数。关于如何枚举一个进程或者线程的窗口,可以参考GetWindowThreadProcessId。也可以用约定的暗号来识别,例如创建一个对象,具有很难重复的属性,然后查找系统中是否已经有了同样属性的对象。通常创建互斥体(MUTEX)或者具有指定窗口类的窗口(不适用于对话框,因为对话框的窗口类是#32770)。一些其他的方法包括检查窗口过程处理特定消息的返回值、检查窗口的约定属性等等。建议用互斥体对创建对象的代码加锁,以避免同时创建两个对象。

有的程序在退出之前需要把命令行参数传递给现存的实例,例如一些文档处理程序和浏览器。官方的方法是在注册表中对应的文件类型中注册DDL命令:HKCR/filetype/shell/open/ddeexec = Open("%1")。 遗憾的是,MFC只在CFrameWnd类里面实现了对DDE的封装。如果你的程序不基于文档视图框架,那么实现DDE支持是很麻烦的事情。幸好你不必这么做,比较简单的方法是发送一个消息——例如WM_COPYDATA——到找到的现存实例窗口,传递命令行参数作为参数。

有的程序,例如IE或者资源管理器,Office等,使用多顶层窗口来替代应用程序的多个实例。尽管MFC7的应用程序向导提供了多顶层窗口的选项,但是这样的实现是不完美的——所有顶层窗口仍然在同一个线程内,使得每个顶层窗口没有单独的状态。举例来说,如果一个浏览器窗口显示了一个消息框,那么其他顶层窗口也会挂起。使用腾讯浏览器的人可能对这种情况很是熟悉。

MFC示例MTMDI演示了如何在MDI程序中在单独的线程中创建窗口,以及相应修改消息和命令传递过程。虽然这样应用程序改动最少,但是这打破了DOC/View架构,而且顶层窗口还是在一个线程中。
用对话框作为顶层窗口是最简单的,在单独的线程中创建对话框也很容易,但是在对话框上创建工具栏要编写一些额外的代码来模仿框架的行为,而且没有文档视图的支持,需要自己编写打开文档和网址的代码。还有一些其它的副作用,例如需要在对话框上实现工具栏、状态栏菜单的自动更新,不能通过自定义窗口类的办法查找现有的应用程序实例等等。

编写多顶层窗口文档处理程序时,在现存实例运行时,在资源管理器打开新的文件的实现是,新的实例发送命令参数到现存实例,创建一个新的顶层窗口之后退出。有很多种方法获得这个参数。MFC程序当然也是C++程序,所以标准C的argc和argv主函数入口参数仍然有效。但是MFC提供了命令行处理的封装类CCommandLineInfo,支持了标准的命令行开关的分析。但是更有效地处理命令行参数的办法还是采用MSJ1999年10月中的C++Q&A专栏的CCommandLineInfoEx类。它提供了自定义命令行开关分析的方法。

No comments: