Win32基于事件驱动的消息机制

消息机制和绘图机制是微软Windows及其周边其它产品与生俱来的,是Win 系列OS作为一个操作系统进行微机内部实现的二大支柱和特征,消息系统是Windows下一切应用程序间,包括Windows自身,进行交互和通讯的渠道,是Windows实现对运行在其下的所有应用程序进行控制及应用程序对Windows进行响应的解决手段,因此对Windows的编程,无论是在哪种 语言规范和IDE 下,都不可避免地要涉及到消息处理,虽然有些编程语言如 VB 用事件驱动编程机制在很大程度上封装了消息的复杂性,但若要深入Win32编程,就必须学习Windows的消息系统,正如游戏编程要掌握Win的绘图机制一样,而只要你一旦深韵了这二大支柱和基本,你就掌握了Win32编程的根本。。
       

消息的产生来源于系统事情(包括计时器事件)和用户事件,Windows用消息来调入和关闭(还有其它处理,如绘制一个窗口等)应用程序,一个典型表现是在关机操作中,Windows发一个关机的消息给所有正在运行的应用程序,告知它们退出内存,此时,应用程序用回应消息的方法来响应OS,因此,消息是应用程序与WinOS交互的手段..
      

消息的主体是应用程序之间和应用程序与 OS 之间,(这是通俗的说法,其实在一个应用程序的内部,各窗口组件之间也存在着消息的流动,窗口组件与它们的父窗口和上层窗口之间当然也有消息的传递过程("命令传递",后面在跟踪一个消息的路径中将会详谈)Windows内部即时流动的消息数量是如此的宠大,程序实现之外的手工分析是一种很自不量力的事情)消息的最终主体却是窗口与窗口之间,窗口与OS之间 - 因为在MFC的技术规范里,只有窗口进程才能发送和接收一个消息并处理它,当然一些非界面窗口类如文档类也能处理一个消息,消息的最终归宿是某个窗口类的成员函数,也就是进入消息处理函数被处理,或被某个非界面类也就是内部处理类如文档类处理,系统中默认的窗口类和用户注册的窗口类都有进程,都能在内存中创建实在的窗口对象,窗口对象和窗口类接收和处理(千万注意:接收一个消息和处理一个消息是相差甚大的二个过程,后面将在讨论重定向一个消息技术时将谈到)发往它或由它主动发往别的窗口进程或OS的消息,修改窗口进程干涉窗口进程对消息的处理过程(而不是接收过程,这个区别的详细解释请参见后面从"注意消息泵并不是一个.."起的文字)是可能的(窗口进程只是一段函数),但是如果这个窗口进程属于别人,如系统的窗口类,你将没有源程序进行修改,但却可以用消息重定向的技术加以干涉,比如用户自定义的窗口类,用户完全可以自定义它的窗口进程,编写自己的消息泵,实现对消息的重定向,编写用户自己的消息泵属于Win32编程中重定向一个消息的七大技术之一。      

不要觉得奇怪,虽然我们拥有众多“所见即所得”的编程方式来开发众多界面精美的应用程序,这些可视化的编程环境提供了大量的类库和控件,但是在开发者享受方便的同时,他们的手脚已经不知不觉的受到了限制,有很多深入到Windows内部的操作它们无法完成,为什么?因为所用的类库不支持。

  事实上这些类库与控件都是架构在Window API的基础上面API即 Application Programming Interface - 应用编程接口的缩写,它不仅为应用程序所调用,同时也是Windows的一部分,Windows自身的运行也调用这些API函数。要了解如何使用API就必须了解一些Windows的运行机制。

  简单地说,Windows是由事件驱动的抢占式多任务操作系统。事件驱动是相对于过程驱动而言的,它改变了原来文件的顺序执行方式;Windows既然是多任务系统,就必须能同时处理多个事件,系统为应用程序生成一个消息队列,消息在上面被张贴和发送,应用程序只要从其消息队列中取出消息,然后一一执行就可以了。

  现在,我将使用最最基本的范例程序 HelloWin 来说明WIN32 API的运行机制。首先,一个程序一定要有进入点,Win32 App的进入点函数的名称是WinMain,它的原型如下:

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine,int nCmdShow)

hInstance是所谓的“实例句柄”,它是一个数值,当程序在Windows下运行的时候,它被用来唯一的标示这个程序,虽然用户可能同时运行多个同一个程序,即运行多个“实例”,我们可以看到,每一个实例都有不同的hInstance值。

hPrevInstance,简单地说就是没用...它是存在于16Windows程序中的,在编写Windows 9x/NT/2000 程序的时候,总应该是NULL

szCmdLine是一个指针,指向一个以0为终结的字串,里面包含传给该程序的命令行参数,如果想要让程序处理命令行,那么这个参数就有用了。

iCmdShow参数是一个数值,指示窗口将如何被显示,这个数值由在Windows下运行该程序的程序所决定,通常是SW_SHOWNORMAL

接下来是注册一个窗口类,窗口总是从窗口类的基础上创建的,窗口类用以标示处理窗口消息的窗口过程,注册窗口类时使用 RegisterClassEx() 函数,它只需要一个参数,一个指向类型为 WNDCLASSEX 的结构指针。

具体注册初始是这样的:
WNDCLASSEX wcex;

wcex.cbSize = sizeof(WNDCLASSEX);     //结构的大小

wcex.style = CS_HREDRAW | CS_VREDRAW; //类风格
wcex.lpfnWndProc = (WNDPROC)WndProc; 
 //窗口类的窗口过程
wcex.cbClsExtra = 0;
            //在类结构中预留的空间
wcex.cbWndExtra = 0;
        //Windows内部保存的窗口结构中预留的空间
wcex.hInstance = hInstance;
        //程序的实例句柄
wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_EXAMPLE);
  //程序图标
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
         //结构的大小
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
  //指定窗口的背景颜色
wcex.lpszMenuName = (LPCSTR)IDC_EXAMPLE;
         //菜单
wcex.lpszClassName = szWindowClass;
           //类名,和程序名相同
wcex.hIconSm = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL); //
也是程序图标

return RegisterClassEx(&wcex);

接下来,定义一个HWND,然后使用 CreateWindow() 函数,原型如下:
HWND CreateWindow(
  LPCTSTR lpClassName,    // 窗口类名
  LPCTSTR lpWindowName,  // 窗口标题
  DWORD dwStyle,       // 窗口风格
  int x,             // 初始
  int y,             // 初始y
  int nWidth,          // 窗口宽 
  int nHeight,         // 窗口高 
  HWND hWndParent,     // 父窗口句柄 
  HMENU hMenu,       // 菜单句柄
  HINSTANCE hInstance,   // 实例句柄
  LPVOID lpParam       // 创建参数
);

在 CreateWindow() 调用返回之后,Windows内部已经创建了这窗口。但是窗口并为显示,还需要两个调用,一个是 ShowWindow(hwnd, iCmdShow):第一个参数是刚刚创建的窗口句柄,第二个参数是传递给WinMainnCmdShow;另一个是 UpdateWindow(hwnd) ,导致客户区域被绘制。

接下来,程序通过执行一块被称为“消息循环”的代码从消息队列中取出消息

while (GetMessage(&msg, NULL, 0, 0)) 
{
  if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) 
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
}

消息循环以 GetMessage 开始,它从消息队列中取出一条消息,只要从消息队列中取出消息的 Message 域不为 WM_QUITGetMessage 就返回一个非零值,否则将导致程序退出消息循环,然后程序中止,返回 msg 结构的 wParam 参数。在循环中,TranslateMessage将 msg 结构的内容进行修改,而DispatchMessage 找出准备调用的窗口过程。

上面进行的仅仅是准备性工作:注册窗口类、创建窗口、显示窗口、进入消息循环取出消息而实际的动作都发生在窗口过程中。

LRESULT CALLBACK WndProc(HWND hWnd, //刚刚创建的窗口句柄
  UINT message,           //得到的消息
  WPARAM wParam, 
  LPARAM lParam           //消息的进一步详细的参数
)

在程序中窗口过程通常是命名为 WndProc 的函数,其实窗口过程可以任意的命名,一个Windows程序可以包含多个窗口过程,一个窗口过程总是与调用了 RegisterClassEx 注册的窗口类相关联,CreateWindow 函数根据窗口类来创建窗口,但是一个窗口类可以被用来创建多个窗口。

消息收到之后,接下来应该根据消息的不同来进行处理
switch(message)
{

 case ...:
 ...
 ...

HelloWin程序只需要处理两条消息,即 WM_PAINT 和 WM_DESTROY

WM_PAINT 消息在Windows程序中的地位极其重要,当窗口客户区的一部分或者全部变为“无效”,必须进行刷新的时候,将由这条消息通知程序。

为什么客户区域会变得无效呢?在创建窗口的时候,整个客户区都是无效的,因为还没有画任何的东西。第一条 WM_PAINT 消息指示窗口过程在窗口上面画一些东西;还有在用户改变了窗口的大小之后,客户区域重新变得无效,除此之外最小化窗口之后再还原、窗口的一部分被覆盖,都会引发这条消息。

WM_DESTROY消息则是当用户按下“关闭”按钮的时候被触发,标准的处理方法是调用PostQuitMessage 将一条 WM_QUIT 消息插入消息队列,这将使得 GetMessage 函数调用返回0,从而退出消息循环,结束整个程序。

其实,从上面可以看出,Windows程序的这种运行机制并不是很难理解,真正困难的是不知道调用什么函数去完成想要的操作,以及怎样调用那些函数,从而灵活的进行底层API程序开发,这是一个循序渐进的积累过程,没有捷径可走的。请各位一定要记住。 

附注

MFC中有七种技术可以用来重定向一个消息,它们是:1,子分类2,超分类3,OnCmdMsg(),4,SetCapture5,编写自己的消息泵,6SetWindowsHookEx(),人们常说的钩子函数,便是其中之一.
     
在谈完消息泵的概念后,我们将一步一步追踪一个消息在系统中的路径,然后才能讨论对它的重定向。
     
消息泵并不是一个窗口类的窗口进程,虽然它们都是函数,同样都对注入到这个窗口进程的消息进行工作,而并不最终处理消息本身(上面已经说到原因),消息泵是一个通俗的说法,它只与消息被发往窗口进程后的接收工作有关而不与处理过程有关(上面也已经说到消息的接收和处理是二不同过程),而窗口进程恰恰相反它只与处理有关不与接收有关下面开始详述。
    
消息泵被包含 CWinApp 的成员函数Run()..

一、Win32编程基本概念 1、消息驱动 在介绍Windows消息驱动概念之前,我们首先来回顾面向过程的程序结构:main()程序有明显的开始、中间过程和结束点,程序是围绕这个过程编写好相关的子过程,再把这些子过程串联在一起。程序编好以后,该过程也就确定了,程序必须按照规定好的顺序执行:是否需要用户的输入、输入什么、程序取得用户输入以后做什么处理,处理完毕将结果显示给用户。该过程一旦确定,程序的执行过程也是固定的,用户不能干预。 而Windows编程所采用设计思想是:消息驱动,又叫做事件驱动。在这种程序结构中,程序没有明显的开始、结束,程序流程的控制由各种随机发生、不确定、没有预先设定顺序的事件的发生来触发。是一个不断产生消息和处理消息的过程。 也就是说程序一运行开始处于等待消息状态,取得消息以后,就对该消息做出相应的处理,完成处理以后又进入等待消息的状态。这种程序结构与Windows操作系统结合非常紧密,最明显一点就是消息的管理是由操作系统完成的。应用程序从操作系统获得消息有两种方式:一种就是应用程序调用Windows提供的消息获取函数;另外一种就是回调函数,由操作系统自己调用。 这种消息驱动机制,有点像银行的柜台业务:早上八点,银行开门(Windows应用程序开始运行),每个营业员(Windwows线程)回到自己的柜台开始办公。如果有顾客来办理相关业务(相当于Windows消息),那么对应的业务员就进行处理。顾客来办理业务的时间以及业务类型都是随机的,如果某一时刻没有顾客办理业务并且没有到下班时间(Windows应用程序退出)的话,那么相关的业务员进入等待状态。所有的业务员不断重复该过程,直到下班(Windows应用程序退出)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值