一引言 随着Windows操作系统的不断推广众多软件开发包都提供有开发基于Windows平台应用软件的功能虽然这些开发包不尽相同流行的有Visual C++Visual BasicDelphiC++ Builder 等多种但由这些不同语言开发的软件有一点却是相同的都是运行于Windows 操作平台都必须接受Windows 的运行机制作为Windows 操作系统灵魂的消息机制也就必然为众多用不同语言开发的Windows操作系统下运行的应用程序所接受因此要编写深入的Windows程序就必须对Windows的运行机制有很好的认识和理解本文下面将对Windows操作系统下的消息运行机制做较为深入的剖析 二Windows事件驱动机制 我们当中不少使用VCDelphi等作为开发语言的程序员是一步步从DOS下的BasicC++中走过来的而且大多在刚开始学习编程时也是先从DOS下的编程环境入手的因此在习惯了DOS下的过程驱动形式的顺序程序设计方法后往往在向Windows下的开发环境转型的过程中会对Windows所采取的事件驱动方式感到无法适应因为DOS和Windows这两种操作系统的运行机制是截然不同的DOS下的任何程序都是使用顺序的过程驱动的程序设计方法这种程序都有一个明显的开始明显的过程以及一个明显的结束因此通过程序就能直接控制程序事件或过程的全部顺序即使是在处理异常时处理过程也仍然是顺序的过程驱动的结构而Windows的驱动方式则是事件驱动的即程序的流程不是由事件的顺序来控制而是由事件的发生来控制所有的事件是无序的所为一个程序员在编写程序时并不知道用户会先按下哪个按纽也就不知道程序先触发哪个消息因此我们的主要任务就是对正在开发的应用程序要发出的或要接收的消息进行排序和管理事件驱动程序设计是密切围绕消息的产生与处理而展开的一条消息是关于发生的事件的消息 三Windows的消息循环 Windows操作系统为每一个正在运行的应用程序保持有一个消息队列当有事件发生后Windows并不是将这个激发事件直接送给应用程序而是先将其翻译成一个Windows消息然后再把这个消息加入到这个应用程序的消息队列中去应用程序需要通过消息循环来接收这些消息在MFC中使用了对WinAPI进行了很好封装的类库虽然可以为编程提供一个面向对象的界面使Windows程序员能够以面象对象的方式进行编程把那些进行SDK编程时最繁琐的部分提供给程序员使之专注于功能的实现但是由于引入了很好的封装特性使我们不能直接操纵部分核心代码对于消息的循环和接收也只是通过类似于下面的消息映射予以很简单的表示 BEGIN_MESSAGE_MAP(CTEMMSView CFormView) //{{AFX_MSG_MAP(CTEMMSView) ON_WM_LBUTTONDOWN() ON_COMMAND(ID_OPENDATA OnOpenData) ON_WM_TIMER() ON_WM_PAINT() //}}AFX_MSG_MAP END_MESSAGE_MAP() 虽然上述消息映射在编程过程中处理消息非常简练方便但显然是难于理解消息是如何参与循环和分发的因此有必要通过SDK(Software Developers Kit软件开发工具箱)代码深入到被MFC封装的Windows编程的核心中来研究其具体是如何工作的在SDK编程中一般是在Windows应用程序的入口点WinMain函数中添加处理消息循环的代码以检索Windows送来的消息然后WinMain再把这些消息分配给相应的窗口函数并处理它们 …… MSG msg; //定义消息名 while (GetMessage (&msg NULL )) { TranslateMessage (&msg) ; //翻译消息 DispatchMessage (&msg) ; //撤去消息 } return msgwParam ; 上述几句虽然简单但却是所有Windows程序的关键代码担负着获取解释和分发消息的任务下面就重点对其功能和作用进行分析 MSG结构在头文件中定义如下 typedef struct tagMSG { HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; } MSG *PMSG; 其数据成员的具体意义如下 hwnd消息将要发送到的那个窗口的句柄用这个参数可以决定让哪个窗口接收消息 message消息号它唯一标识了一种消息类型每种消息类型都在Windows文件进行了预定义 wParam一个位的消息参数这个值的确切意义取决于消息本身 lParam同上 time消息放入消息队列中的时间在这个域中写入的并非当时日期而是从Windows启动后所测量的时间值Windows用 这个域来使用消息保持正确的顺序 pt消息放入消息队列时的鼠标坐标 消息循环以GetMessage调用开始它从消息队列中取出一个消息该函数的四个参数可以有控制地获取消息第一个参数指定要接收消息的MSG结构的地址第二个参数表示窗口句柄一般将其设置为空表示要获取该应用程序创建的所有窗口的消息第三四参数用于指定消息范围后面三个参数被设置为默认值用于接收发送到属于这个应用程序的任何一个窗口的所有消息在接收到除WM_QUIT之外的任何一个消息后GetMessage()返回TRUE如果GetMessage收到一个WM_QUIT消息则返回FALSE以退出消息循环终止程序运行因此在接收到WM_QUIT之前带有GetMessage()的消息循环可以一直循环下去当除WM_QUIT的消息用GetMessage读入后首先要经过函数TranslateMessage()对其进行解释但对大多数消息来说并不起什么作用这里起关键作用的是DispatchMessage()函数把由GetMessage获取的Windows消息传送给在MSG结构中为窗口所指定的窗口过程在消息处理函数处理完消息之后代码又循环到开始去接收另一个消息这样就完成了一个完整的消息循环 由于Windows操作系统是一种非剥夺式多任务操作系统只有在应用程序主动交出CPU控制权后Windows才能把控制权交给其他应用程序在消息循环中一定要有能交出控制的系统函数才能实现协同式多任务操作能完成该功能的只有GetMessagePeekMessage和WaitMessage这三个函数如果在应用程序中长期不去调用这三个函数之一其他任务则无法执行GetMessage函数在找不到等待应用程序处理的消息时会自动交出控制权由Windows把CPU的控制权交给其他等待获取控制权的应用程序由于任何Windows应用程序都含有一个消息循环这种隐式交出控制权的方式可以保证合并各个应用程序共享控制权一旦发往该应用程序的消息到达应用程序队列即开始执行GetMessage语句的下一条语句使用GetMessage函数的消息循环在消息队列中没有消息时将等待如果需要可以利用这段时间进行I/O端口操作等耗时操作不过需要在消息循环中使用PeekMessage函数来代替GetMessage使用PeekMessage的方法同GetMessage类似下面是一段使用PeekMessage函数的消息循环的典型例子 MSG msg; BOOL bDone=FALSE; do{ if(PeekMessage(&msgNULLPM_REMOVE)){ if(ssage==WM_QUIT) bDone=TRUE; else{ TranslateMessage(&msg); DispatchMessage(&msg); } } //无消息处理进行长时间操作 else{ ……//长时间操作 } }while(!bDone) …… 无论应用程序消息队列中是否有消息PeekMessage函数都立即返回如果希望等待新消息入队可以利用无返回值的函数WaitMessage配合PeekMessage进行消息循环 四对Windowds消息的处理 窗口过程处理消息通常以switch语句开始对于它要处理的每一条消息ID都跟有一条case语句这在功能上同MFC的消息映射有些类似 switch(uMsgId) { case WM_TIMER //对WM_TIMER定时器消息的处理过程 return ; case WM_LBUTTONDOWN //对WM_ LBUTTONDOWN鼠标左键单击消息的处理过程 ruturn ; …… default: //其他消息由这个默认处理函数来处理 return DefWindowProc(hwnduMsgIdwParamlParam); } 在处理完消息后必须返回这很重要否则Windows将要不停地重试下去对于那些在程序中不准备处理的消息窗口过程会把它们都扔给DefWindowProc进行缺省处理而且还要返回那个函数的返回值在消息传递层次中可以认为DefWindowProc函数是最顶层的函数该函数发出WM_SYSCOMMAND消息由系统执行Windows环境中多数窗口所公用的各种通用操作如更新窗口的正文标题等等 在MFC下可以用下述部分代码实现与上述SDK代码相同的功能 BEGIN_MESSAGE_MAP(CTEMMSView CFormView) //{{AFX_MSG_MAP(CTEMMSView) ON_WM_LBUTTONDOWN() ON_WM_TIMER() //}}AFX_MSG_MAP END_MESSAGE_MAP() 小结Windows环境提供有非常丰富的系统资源在这个基础上可以编制出能满足各种各样目标功能的应用系统要深入Windows编程就必须首先对Windows系统的运行机理有很好的认识本文仅针对Windows的一种重要运行机制消息机制作了较深入的剖析和阐述对培养在Windows下的编程思想有一定的帮助对某些相关问题的详细论述可以参考MSDN在线帮助的SDK Reference部分 |