自动保存程序的设计与实现
段通晓 吕强 杨季文
摘要:介绍了自动保存程序在智能UPS监控软件中的应用,给出了在Windows环境下如何用VC++实现该自动保存程序的方法。
关键词:智能UPS,自动保存,多线程,模拟键盘输入
1 引言
随着信息技术迅速发展, UPS(Uninterruptible Power Supply)不间断电源系统应运而生,并被广泛地应用到国民经济的各个领域。作为新一代的智能UPS,它还要求能定时关机、定时开机,当供电发生故障后,它能通知计算机自动保存用户数据和安全关机。监控已成为当今智能UPS一个不可或缺的功能,自动保存功能则是监控软件中的一个重要组成部分。自动保存功能是指在没有用户干预的情况下让计算机保存尚未存盘的文档。本文主要讨论了在Windows环境下自动保存程序的设计与实现。
2 智能UPS监控软件
计算机与UPS电源通过RS232串口进行通信,监控程序周期性地对UPS进行查询。如果UPS出现不正常,监控程序就从RS232串口中获取故障信息,立即将其显示在屏幕上提醒用户,并把故障发生的时间和类型记录在日志中以供日后查询和分析。同时,监控程序在屏幕上进行倒计时,提醒计算机用户采取措施。如果倒计时为零,用户还未采取任何措施,则触发计算机自动保存软件,然后安全关闭计算机。如果供电恢复正常,计算机还未关机,则回到监控程序查询状态。监控程序的示意图如图1所示。
图1 监控程序
3 自动保存程序的设计与实现
3.1 设计思想
作为智能UPS监控软件的一部分,自动保存软件受UPS监控软件的触发而启动。当监控软件检测到电源供电发生故障时,便向用户发出警报,同时开始倒计时。若在倒计时为零时电源还未恢复正常,监控软件将触发自动保存软件。自动保存软件的主要工作是保存未存盘的文档。如果是新文档的话,则随机生成文件名,并保存在固定的文件夹中。
我们无法知道某个程序的内部运行机理,也就很难直接操纵这个程序,让它保存文档。但是Windows程序都是基于消息驱动的,程序的各种功能实际上是对不同消息的响应,因此可以考虑给程序发送消息来间接实现保存文档的目的。通过观察我们发现,大多数应用程序在关闭时会提示用户保存文件,如果是新文档的话,还会提示用户输入文件名。我们正是利用应用程序关闭时的这一特性来设计自动保存程序。基本思路为:自动保存程序给某个应用程序发送关闭程序的消息,让它对此消息作出响应。如果有文档没有保存,该应用程序则会弹出对话框询问是否保存文档。这时自动保存程序应该发送消息表示需要保存。如果文档不是第一次保存,该应用程序就用原来的文件名保存文档,否则会弹出对话框要求输入文件名。这时自动保存程序应该将文件名发送消息通知该应用程序,该应用程序会以此文件名保存文档。
3.2 具体实现
我们在Windows2000环境下用VC++6.0开发完成了自动保存程序,程序框图如图2所示。
图2 自动保存程序
3.2.1 获得所有窗口程序
首先,我们需要获得任务栏上列出的所有窗口程序,Windows系统提供了API函数EnumWindows用于枚举所有的顶层窗口:
BOOL EnumWindows( WNDENUMPROC lpEnumFunc, LPARAM lParam );
参数说明:LpEnumFunc为指向回调函数的指针,lParam为传给回调函数的参数。
在回调函数中加上过滤条件IsWindowVisible(hWnd) && !GetParent(hWnd)用于获得可见的并且没有父窗口的窗口程序,即任务栏上列出的所有窗口程序。
枚举出任务栏上列出的所有窗口程序后,用链表对其进行管理,生成的链表如图3所示。
图3 进程链表
链表结构如下:
struct WindowHandle
{
HWND hWnd; //窗口句柄
WindowHandle *next;
};
struct WindowProcess
{
DWORD ProcessId; //进程标识
int WindowCount; //使用该进程的窗口数
WindowHandle *pWindowHandle; //窗口句柄链表
WindowProcess *next;
};
窗口程序的进程标识通过Windows系统API函数GetWindowThreadProcessId获得:
DWORD GetWindowThreadProcessId( HWND hWnd, LPDWORD lpdwProcessId );
参数说明:hWnd为窗口句柄,lpdwProcessId为存放进程标识的指针。
在WindowProcess结构中定义窗口句柄链表而不是窗口句柄,主要是考虑到有可能会出现多个窗口使用一个进程的情况,如Microsoft Word 2000。
3.2.2 保存文档
接下来依次取出链表表头的结点,向其记录的窗口发送WM_QUERYENDSESSION消息。发送消息用到如下windows系统API函数:
LRESULT SendMessage( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam );
参数说明:hWnd为窗口句柄,Msg为待发送的消息,wParam为第一个消息参数,lParam为第二个消息参数。
窗口程序收到WM_QUERYENDSESSION消息后会作出响应,如果文件没有保存,则会弹出对话框询问是否保存文档,焦点通常在确定按钮上。正因为焦点在确定按钮上,我们可以模拟键盘输入回车通知窗口程序需要保存文档。如果文档不是第一次保存,窗口程序就用原来的文件名保存文档。否则会弹出对话框要求输入文件名,焦点通常在文件名编辑框中。正因为焦点在文件名编辑框中,我们可以模拟键盘输入随机生成的唯一文件名和回车通知窗口程序以该文件名保存文档。窗口程序保存文档后,我们关闭窗口程序,并将保存文档的文件名写入日志,这样用户重新开机后可以通过日志获知哪些文档被保存了。
这里我们先要确定一个窗口是不是响应WM_QUERYENDSESSION消息而弹出的询问对话框。考虑到询问对话框隶属于产生它的窗口,也就是它的父窗口,我们通过Windows系统API函数GetForgroundWindow()获得前台窗口,如果它有父窗口并且它的父窗口就是我们发送WM_QUERYENDSESSION消息窗口的话,就表明它是响应WM_QUERYENDSESSION消息而弹出的询问对话框,这时我们就可以模拟键盘输入来给这个询问对话框发送消息了。
模拟键盘输入用到如下Windows系统API函数:
VOID keybd_event( BYTE bVk, BYTE bScan, DWORD dwFlags, DWORD dwExtraInfo );
参数说明:bVk为虚拟按键代码,bScan为硬件扫描码,dwFlags为不同选项的标志,dwExtraInfo为附加信息。
3.2.3 使用定时器
在向某个窗口发送WM_QUERYENDSESSION消息之前需要启动定时器,当预定时间到了,如果窗口程序不能按照上述过程保存文档并关闭窗口程序,可能会影响到自动保存程序对其它窗口程序的处理,这时就强行结束该窗口进程。
启动定时器用到如下Windows系统API函数:
MMRESULT timeSetEvent( UINT uDelay, UINT uResolution, LPTIMECALLBACK lpTimeProc, DWORD dwUser, UINT fuEvent );
参数说明:uDelay为定时器事件时间间隔,uResolution为定时器事件的最小分辨率,lpTimeProc为指向回调函数的指针,dwUser为回调数据,fuEvent为定时器事件类型。
结束进程用到如下Windows系统API函数:
BOOL TerminateProcess( HANDLE hProcess, UINT uExitCode );
参数说明:hProcess为进程的句柄,uExitCode为进程的退出代码。
3.2.4 多线程协作
整个程序需要用多个线程来协调动作,这里我们用了三个线程:主线程、Control线程和Handle线程。主线程用于向窗口程序发送WM_QUERYENDSESSION消息;Control线程用于捕获窗口程序响应WM_QUERYENDSESSION消息后弹出的提示信息窗口,然后通知Handle线程;Handle线程用于模拟键盘输入,对提示信息窗口作出相应处理。Control线程和Handle线程之间的协作通过核心对象event来完成,Control线程捕获到提示信息窗口,则激活event对象,而Handle线程一直等待着event对象被激活,只有event对象被激活后才能模拟键盘输入,对提示信息窗口作出处理。
创建线程的Windows系统API函数为:
HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId );
参数说明:lpThreadAttributes为指向线程安全属性结构的指针,dwStackSize为分配给线程的堆栈的初始大小,lpStartAddress为线程函数的起始地址,lpParameter为传递给线程的参数,dwCreationFlags为线程创建时是否挂起的标志,lpThreadId用于存放线程的标识。
创建核心对象event的Windows系统API函数为:
HANDLE CreateEvent( LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPCTSTR lpName );
参数说明:lpEventAttributes为指向Event安全属性结构的指针,bManualReset指定是手工还是自动重置event对象为不激活状态,bInitialState为event对象的初始状态,lpName为event对象的名称。
激活event对象用到如下Windows系统API函数:
BOOL SetEvent( HANDLE hEvent );
参数说明:hEvent为event对象的句柄。
等待核心对象用到如下Windows系统API函数:
DWORD WaitForSingleObject( HANDLE hHandle, DWORD dwMilliseconds );
参数说明:hHandle为等待对象的句柄(代表一个核心对象),dwMilliseconds为等待的最长时间。
4 结束语
大多数应用程序关闭时都有相似的步骤,自动保存程序的设计正是利用了这一特性。应该说根据这一思想实现的程序通用性还是很高的,经过测试,我们发现该程序可以很好地保存大多数常用的应用程序文档,如记事本、写字板、画图、Microsoft Word、Microsoft Excel、Microsoft Powerpoint等。
参考文献
[1] 陈静 何湘宁 智能UPS的研究与发展 中国电力 2002,2
[2] (美)Jim Beveridge & Robert Wiener著;侯捷译 Win32多线程程序设计 华中科技大学出版社 2002,1
[3] (美)微软公司 Microsoft Developer Network