摘 要:要实现对Windows下运行的程序进行自动控制是很多行业软件遇到的问题,因为往往这些程序是由设备厂家附带的应用软件,用户无法改变程序的工作方式。本文在理解Windows消息机制的基础上,总结出一种利用消息机制实现对运行中的程序进行软件控制的方法。
关键词:windows;消息;窗口;句柄;程序控制
1 引言
Windows作为当前最普及的计算机操作系统,已经深入到各行各业当中,包括工业控制领域。但在这些应用当中,经常会遇到Windows程序自动控制的问题。所谓的程序控制是指通过人工或者自动的方式对正在执行的程序进行操作,以实现程序本身的功能。经常会遇到应用程序需要定时进行操作,且这些操作是一些机械的动作,且定时地按照固定的操作顺序进行,如果人工操作会存在遗忘和操作错误等可能,而程序是由第三方编写的,要去修改程序或者增加自动操作功能需要耗费很多的成本甚至是不可能。笔者利用Windows的消息机制来实现对程序的自动控制,省去了人工的操作。总体思路是,根据所要实现的控制功能,定义一系列的动作,通过向程序的适当窗口发送Windows消息,模拟人工操作动作实现。本文在简单介绍Windows消息机制后总结出实现程序自动控制的方法。
2 Windows消息机制概述
Windows系统是一个消息驱动的操作系统。当用户进行了输入或是窗口的状态发生改变时系统都会发送消息到某一个窗口。系统将会维护一个或多个消息队列,所有产生的消息都会被放入或是插入队列中。系统会在队列中取出每一条消息,根据消息的接收句柄而将该消息发送给拥有该窗口的程序的消息循环。每一个运行的程序都有自己的消息循环,在循环中得到属于自己的消息并根据接收窗口的句柄调用相应的窗口过程。而在没有消息时消息循环就将控制权交给系统,所以Windows可以同时进行多个任务。这就是Windows操作系统的消息机制。
2.1 消息的数据结构
在Windows程序中,消息是由MSG结构体来表示的。MSG结构体的定义如下:
(1)标准消息
除WM_COMMAND之外,所有以WM_开头的消息都是标准消息。从CWnd派生的类都可以接收到这类消息。
(2)命令消息
来自菜单、加速键或工具栏按钮的消息均是命令消息。这类消息都以WM_COMMAND形式呈现。在MFC中,通过菜单项的标识(ID)来区分不同的命令消息;在SDK中,通过消息的wParam参数识别。从CCmdTarget派生的类都可以接收到这类消息。
(3)通告消息
由控件产生的消息,例如按钮,列表框的选择等都会产生通告消息,目的是为了向其父窗口(通常是对话框)通知事件的发生。这类消息也是以WM_COMMAND形式呈现的。从CCmdTarget派生的类,都可以接收到这类消息。
Windows把非命令消息直接发送给窗口类对象,该窗口类中用于处理该消息的处理函数 将被调用。但是,对于命令消息,将把命令消息发送给多个候选对象(称为命令目标),目标中总有一个将调用该命令的处理函数。
3 Windows程序控制技术
根据程序实现的功能,让程序在合适的时刻模拟Windows系统向目标应用程序的合适窗口发送消息,Windows将此消息加入到Windows消息队列中,应用程序处理属于各自窗口句柄的消息。上面介绍了Windows的消息机制,那么要实现程序控制,还须要弄清楚两个问题,如何获取应用程序的对应窗口的句柄和如何向该窗口发送消息。
3.1 获取窗口句柄
Windows中用窗口句柄(Handle)来标识一个窗口。有句柄的控件本质上都是一个窗体(window),它们可以独立存在,可以作为其它控件的容器,而没有句柄的控件,如Label,是不能独立存在的,只能作为窗口控件的子控件。如何正确地获得目标窗口句柄是实现应用程序控制的基础。
窗口分为主窗口和子窗口,并且子窗口中还有子窗口。我们首先要分辨出窗口的从属关系,可以利用SPY++工具来确定每一窗口(包括有句柄的控件)之间的关系,SPY++以清晰的树状结构表示窗口的父窗口和子窗口。要找到某一子窗口,必须首先找出其应用程序的主窗口,在逐级的找出子窗口,直至找到你要控制的控件。
要获得窗体/控件的句柄,你先用SPY++获得它的类名,如任务栏类名为 ""Shell_TrayWnd"",然后用FindWindow即可获得其句柄,如任务栏句柄
hWnd=FindWindow(""Shell_TrayWnd"", Null)
其他窗体也是一样,只要用SPY++选获得窗体(或父窗体)类名,便可以步步用FindWindow或FindWindowEx获得其句柄。
3.2 发送Windows消息到受控窗体
发送消息到指定窗体一般通过以下两个Windows API函数完成的:SendMessage和PostMessage。两个函数的区别在于:PostMessage函数只是向线程消息队列中添加消息,如果添加成功,则返回True,否则返回False,消息是否被处理,或处理的结果,就不知道了。而SendMessage则有些不同,它并不是把消息加入到队列里,而是直接翻译消息和调用消息处理(线程向自己发送消息才是这样),直到消息处理完成后才返回。所以,如果我们希望发送的消息立即被执行,就应该调用SendMessage。还有一点,就是SendMessage发送的消息由于不会被加入到消息队列中,所以通过PeekMessage或GetMessage是不能获取到由SendMessage发送的消息。另外,有些消息用PostMessage不会成功,比如wm_settext。所以不是所有的消息都能够用PostMessage的。
3.3 对不同种类窗口的程序控制
Windows应用程序窗口一般有按钮、文本框、菜单、滚动条等不同的要素组成,只有具有窗口句柄的控件才能够通过消息机制的形式进行控制,如:向文本框输入文字、点击按钮、选择菜单等,而比如Label,并不具备窗口句柄,我们不能通过发送消息进行控制。从上面对消息的介绍中,我们知道,
要控制一个可控的控件,可以通过SendMessage这个API函数发送特定的消息,我们可以借助于SPY++工具,通过工具的监视操作的功能,先认为的进行一次类似操作,再查看该操作所须要的SendMessage函数的参数值。
(1) 控制按钮
按钮的动作一般是鼠标左键点击WM_LBUTTONDOWN,因此wParam参数的值为MK_LBUTTON,lParam是一个32位的值,其高16位放的是该鼠标消息发生的y坐标,低16位放的是x坐标。其实,通过SPY++的监视我们可以知道,鼠标点击动作还有鼠标左键弹起的动作WM_LBUTTONUP,因此,我们还需要再发送这一消息,才完成了按钮被点击的动作。
(2) 控制菜单栏
要实现对菜单的控制,首先要去的菜单的标识信息,Windows应用程序的菜单可以理解为树状结构:树根是主菜单,中间节点为各级菜单,叶节点就是菜单项了。要取得菜单项的标识信息就得从树根出发逐级而下取得了。
用Windows API函数GetMenu取得主菜单的句柄,GetSubMenu取得子菜单的句柄,而取得菜单标识项则调用GetMenuItemID。得到了菜单项的标识后,就可以同过发送WM_COMMAMD消息选择控制菜单项了。
(3) 控制文本框的输入
实现文本的输入,是向文本框发送键盘消息,例如向文本框输入一段文字,文字存放在str中。
char *p=str;
while(*p) SendMessage(Handle,WM_CHAR,*(p++),0)
4 结束语
Windows应用程序所包含的控件有很多种,每一种又有不同的消息,而要实现控制,就要知道完成某一动作所须要的消息步骤,我们要弄清楚这些,可以借助于SPY++这一软件工具。笔者利用Windows消息机制实现了几个进口设备软件的定时自动控制,就如三亚机场的多普勒天气雷达的自动开机程序,大大的节省了人力成本,而且减少了出错的机会,改善了气象服务质量。
参考文献:
[1]孙鑫,余安萍.VC++深入详解[M].北京.电子工业出版社.2006
[2]李久进.MFC深入浅出-从MFC设计到MFC编程[M].武汉.华中理工大学出版社.1999