Win32消息

来源:互联网 发布:http aq.js cj.com 编辑:程序博客网 时间:2024/05/22 05:27
一、消息概念 消息就是在事件驱动模式下,事件发布函数和具体功能执行函数(或者代码段)之间的调用协议,调用协议的执行表现为窗口事件发布函数跟具体功能执行函数(或者代码段)之间的选择关系。 在Windows系统下(下面所述均为Windows系统),对计算机外设的操作,例如当用户敲击键盘键、点击鼠标、热插拔USB盘等,系统都认为外设发生了事件,于是系统调用专职函数就把这些事件进行收集,形成现场信息,赋值给一个叫做“消息结构体”的对象,那么这个被赋予特定意义的消息结构体对象就可以称之为“消息”;然后,专职函数再把这个“消息”投递到(Post)到系统中一个专职消息管理的机构中去,这个过程就完成了消息形成和投递。可以这样想象这个所谓的“消息管理机构”的主要运作: 1、系统每时每刻都有可能发生事件,所发事件都是针对某个或者某些具体应用程序的,所以计算机需要先对这些由事件形成的消息进行集中收集,并完成存操作(即消息投递),一般来说新来的消息都存放在靠后位置,也就是严格按照先来后到的时间顺序进行存操作的; 2、再由系统专职函数对这些消息进行读取,完成取操作。取操作跟存操作刚好相反,是从头开始的,就是先取存放时间最早的那个消息。然后,这些专职函数根据所取的消息的参数的含义,把所取的消息再投递到指定的应用程序的“消息管理机构”中去。 在上面这些过程中,系统内的这个“消息管理机构”就叫做系统消息队列,系统消息队列就是起到集中收集、集中管理外设消息的。但系统设立的“系统消息队列”这个机构不光是为集中管理计算机外设消息的,有时侯它还负责管理系统内部事件所引发的消息的,比如用户登录和注销、进程启动和退出等。 系统消息队列对消息的指派过程跟它自己对消息收集的那个过程有些区别:首先,收集消息过程是个被动过程,系统消息队列不会主动去收集消息,而是由系统的专职函数投递到系统消息队列的,也就是说这个操作由外部调用其接口来完成的;其次,收集消息时,是不管消息来源的。而系统消息队列对消息的指派则是需要关注消息的去向的,一般来说,消息的去向是由消息对象成员hWnd所决定的,请看消息结构体代码如下所示:   typedef struct tagMSG { HWND    hwnd;   // 接受该消息的窗口句柄 UINT      message; // 事件标识符,也就是消息ID WPARAM  wParam; // 事件的特定附加信息,依赖于事件 LPARAM   lParam; // 事件的特定附加信息,依赖于事件 DWORD   time;   // 消息被Post时的时间,以毫秒形式给出 POINT     pt;     // 消息创建时的鼠标/光标在屏幕坐标系中的位置 } MSG; hwnd窗口句柄,窗口可以是任何类型的屏幕对象,例如:窗口、对话框、按钮、编辑框、列表等,它们是可视对象或者隐藏对象等的句柄。 message 1、是区别其它事件的常量,这些常量可以是Windows或者Framework预定义的常量,也可以是用户自定义的常量。 2、事件标识符以常量命名的方式指出事件的本质,其前缀指明系统消息属于的类别。例如:WM_LBUTTONDOWN表示发生了鼠标左键按下事件,WM_SIZE表示发生了窗口布局调整事件,都是窗口消息。 3、当窗口事件发布函数接收到消息之后,就会根据消息ID来决定如何处理消息。例如:WM_PAINT告诉窗口事件发布函数 窗体UI需要重绘。   一个进程,如果包含了消息对象成员所描述的窗口句柄的话,那么该进程就是消息所要去的目的地。再说说系统消息队列把消息投递到应用程序的“消息管理机构”后的事情吧。 跟系统对消息的管理一样,应用程序也有一个叫做“消息管理机构”的机构,该消息管理机构也有两个过程:存和取。存放跟本应用有关的消息,也是按照先来后到的顺序进行的,并也是按照先来后到的顺序读取已经存放的消息,然后根据消息对象成员hWnd把消息投递到具体窗口去。这个“消息管理机构”就是所谓的进程消息队列。 可见所谓消息队列就是有关MSG(消息数据)的一个叫做队列的数据结构,包含两个基本的操作:消息的存和取,并且都是按照时间先后次序进行的。按照所属的不同,它分为系统消息队列和进程消息队列。系统消息队列由操作系统负责维护,进程消息队列由进程负责维护。 是不是每个进程都具有消息队列呢?回答是否定的。在Windows下,只有那些具备窗口(GUI用户接口界面)的进程,才会有消息队列,那些不具备GUI的进程是没有消息队列的。也就是说:操作系统在开启一个新的进程时,并没有为其创建消息队列,而是当进程第一次调用GDI函数后,才创建,并且进程持有窗口时,才持有消息队列。 是不是系统消息队列存取操作就是系统的事件发布函数,而进程消息队列存取操作就是进程的事件发布函数呢?是的,在消息到达具体窗口之前,事件发布函数不是一个孤零零的C/C++意义上的函数,它更像一个机构,包含消息的收、存和发等操作。一般情况下,队列化消息基本上是用户输入的结果,队列消息的操作都是通过消息循环来完成的。对于消息队列而言,所谓的消息循环就是这样一个循环,不断地访问消息队列,检测队列中有没有消息存在,如果有,就取出发布。   消息到达具体窗口后,那么对消息的管理就由窗口过程(窗口事件发布函数)来负责管理了,下面先说说什么是窗口过程。每个窗口在被创建后,都有一个CALLBACK成员函数,一般函数名称为:WndProc,它有四个参数:HWND(窗口句柄),UINT(消息ID),和两个消息参数(wParam, lParam);当窗口接到消息时,就由该函数来执行对消息的处理。因此该CALLBACK函数WndProc就叫做窗口过程,在此处,我称之为:窗口事件发布函数。这是一个纯粹C/C++意义的函数,它对消息的处理,是利用对消息ID的选择来进行的。下面就是一个Win32进程的WndProc函数的示意: //  函数主要功能:  处理被指派到进程主窗口的消息. //  主要消息说明: //      WM_COMMAND: 应用程序的菜单消息(命令消息); //      WM_PAINT:   窗口绘制消息(窗口消息) //      WM_DESTROY: 窗口销毁并使应用程序退出的消息(通知消息) LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {          int wmId, wmEvent;          PAINTSTRUCT ps;          HDC hdc;            switch (message)          {          case WM_COMMAND:  // 命令消息              wmId    = LOWORD(wParam);  wmEvent = HIWORD(wParam);              // 解析具体命令              switch (wmId)              {              case IDM_ABOUT: // 打开关于对话框                   DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);                   break;              case IDM_EXIT:  // 发出退出命令                   DestroyWindow(hWnd);                   break;              default:                   return DefWindowProc(hWnd, message, wParam, lParam);              }              break;            case WM_PAINT:  // 窗口消息              hdc = BeginPaint(hWnd, &ps);              // TODO: Add any drawing code here...              EndPaint(hWnd, &ps);              break;          case WM_DESTROY: // 通知消息(也包含了窗口消息成分)              PostQuitMessage(0);              break;          default:              return DefWindowProc(hWnd, message, wParam, lParam);          }          return 0; } 上面这个WndProc函数就是窗口事件发布函数(很多人称它窗口过程)。窗口事件发布函数就是窗体消息的处理函数,所有与窗体消息有关的功能执行协议都是由它来处理的,同时,窗口事件发布函数又是一个CALLBACK类型的函数,也就是说,用户不需要通过编程来调用该函数,对它的调用是进程来完成的。窗口事件发布函数的特点概括一下有:1、存在依赖于窗体,一个窗体对应一个事件发布函数;2、是窗体的消息处理中心,所有与窗体消息有关的处理都在该函数进行;3、调用是由进程完成的。   那么有了队列化消息概念,相应也有非队列消息的概念。队列化消息是指那些通过投递方式进入消息队列的消息。相应地,那些没有被投递到消息队列,而直接发送给窗口事件发布函数的消息就是非队列消息。非队列消息一经发送发送到窗口,窗口事件发布函数就会对其进行分析处理,按照其message内容进行选择执行对应的代码段。如上面的代码所示:当消息是命令消息时,执行进入到命令消息代码段,首先解析命令参数内容,根据命令来源执行再进一步相应代码,例如,如果是打开关于对话框命令,就执行case IDM_ABOUT段代码,如果是退出命令,就执行case IDM_EXIT段代码,销毁窗体,退出应用程序。窗口事件发布函数对消息的掌控也是通过循环进行的,此处的循环跟消息队列的消息循环形式上一样,都是通过一个循环不断地检查某个地方消息存在与否,不同的是消息队列检查消息是否存在的地方是队列,而窗口事件发布函数的循环检查的地方是所属窗体。 一般情况下,非队列化消息来自呼叫特定的Windows函数。例如:当WinMain呼叫CreateWindow时,Windows将建立窗口并在处理中给窗口事件发布函数传递一个WM_CREATE消息;当WinMain呼叫ShowWindow时,Windows将给窗口事件发布函数传递WM_SIZE和WM_SHOWWINDOW消息;当WinMain呼叫UpdateWindow时,Windows将给窗口事件发布函数传递WM_PAINT消息。也就是说非队列消息将会绕过系统消息队列,直接发送到窗口过程。   消息的简单传递过程: 1、发生用户输入事件,系统专职函数对其加工形成消息,再由这些函数投递到系统消息队列。在系统消息队列进行的消息循环中,消息取操作按照时间先后次序读取消息,根据消息hWnd参数,将消息投递到进程消息队列。在进程的消息循环中,消息的取操作也是按照时间次序读取消息,呼叫DisPatchMessage,根据消息hWnd参数,将消息投递给具体窗口。窗口事件发布函数在接到消息后,根据消息的message参数,进行解析,选择代码段进行执行。 2、对一些特定的窗口函数呼叫,系统产生消息,这些消息被发送到对应窗口,窗口事件发布函数在接到消息后,根据消息的message参数,进行解析,选择功能函数(或者代码段)执行。 3、对一些比较特别的消息的处理:例如WM_PAINT,系统在处理WM_PAINT时,同一个窗口的多个 WM_PAINT被合并成为一个 WM_PAINT 消息, 合并所有的无效区域成为一个无效区域,合并WM_PAIN的目的是为了减少刷新窗口的次数,窗口刷新一次是要消耗很多系统资源的。   窗口事件发布函数一次循环的完成,可以这样理解: 1、窗口事件发布函数在接到一个消息后,需要对消息进行处理,如果正在处理的代码又向该窗体发送消息,则窗口事件发布函数将会中断,转而去处理新的消息,在对新消息处理完毕后,转回处理原来中断地方代码,即产生递归现象。 2、防止消息死锁( Message Deadlocks),假设有线程A和B,现在有以下下步骤就会造成消息死锁: 1) 窗口事件处理函A SendMessage给窗口事件处理函B, 由于SendMessage的特点,A等待消息在B中处理后返回; 2) 窗口事件处理函B收到了窗口事件处理函A发来的消息,并进行处理,在处理过程中,B也向A SendMessgae,然后等待从A返回。然而此时,A正等待从B返回, 无法处理B发来的消息,从而导致了A,B相互等待,形成死锁。可以使用 SendNotifyMessage或SendMessageTimeout来避免出现死锁。 二、消息划分     Windows中的消息种类大体上有系统定义消息(System-Defined Messages)和用户自定义的消息。其中系统定义消息三种:窗口消息、命令消息、通知消息,这是按消息目的、行为进行划分的。 窗口消息:跟窗体变化有关的消息,与窗口的内部运作有关,在Windows中,当窗体发生变化时,窗体与操作系统、窗体与窗体之间都会有消息传递,从而对窗体起到控制作用。所谓的窗体变化包括:窗体被创建、被移动、被改变形状、被销毁、显示和隐藏、被涂画和鼠标键盘等物理设备对其发生的作用等,它是Windows系统中最为常见的消息。其传递路径一般为:窗体<—>操作系统、窗体<-->窗体。 在Windows中,常见窗口消息有: 面向Dialog的:WM_CREATE、WM_PAINT、WM_SIZE、WM_DESTROY、WM_CHAR、WM_TIMER、WM_KEYDOWN、WM_MOVE、WM_SETFOCUS等; 命令消息:跟处理用户请求有关,或者说是跟用户操作有关,属于跟用户相交互的消息。最常见的命令消息就是按钮消息、关闭消息等。其传递路径一般为:用户—>窗体—>窗体。其本质也是一种窗口消息,一种特殊的窗口消息,只不过其起因来源于用户而已。 在Windows中,常见的命令消息有: 面向控件的:例如ListCtrl的NM_CLICK、NM_DBCLICKED、HDN_ITEMCLICKED等、按钮控件的BN_CLICKED、BN_DBCLICKED等。菜单消息就属于命令消息。 通知消息:是指这样一种消息,当控制参数到达某个量级时、满足某个条件时或者接到某个参数值时,系统为进行控制行动而形成的消息。这样的消息跟命令消息有些类似,但是它是系统发出的。它的传递路径:系统-->窗体,跟窗口消息不同的是:窗口消息只是为了调整窗口而发,通知消息为了对系统进行控制行动而发。因为能够进行消息接收的代码都必须依赖于窗体,所以通知消息的路径就是系统-->窗体,再有窗体函数完成对系统的控制动作。这类消息一般表现形式为Notify类消息,其Message, wParam, lParam分别为:WM_NOTIFY, 控件ID,指向NMHDR的指针,NMHDR包含控件通知的内容,可以任意扩展。 这三类消息之间相互贯通,并没有严格的界限,有时候命令消息、通知消息也是为了窗体变化而来的,窗口消息也可以被视为命令和通知。比如说在一个Dialog上放置了一个Edit控件,当用户在Edit中编辑文本时,产生消息纯为命令消息,当文字编辑超越可视区域时,编辑框产生滚动条,此时属于通知消息,通知Edit窗口要调整了。当调整窗口时,又要产生窗口更新消息,此时就属于窗口消息。 用户自定义的消息,对于其范围有如下规定:WM_USER: 0x0400-0x7FFF、0x8000-0xBFFF。     系统保留消息ID的值在0X0000在0X03FF范围,这些值为系统所保留定义,用户自定义消息ID不能够与其相冲突。用户自定义消息ID范围为从WM_USER(0X0400)到0X7FFF,或0XC000到0XFFFF。Windows消息ID概览如下: WM_NULL---0X0000:表示无操作,在消息映射中,如果应用程序试图使接收器对其投递的消息实行忽略处理,则发送WM_NULL类型消息,如果窗体的消息处理函数WindowProc接收到WM_NULL类型消息,则忽略掉。 0X0001----0X0087:窗口消息,例如WM_SIZE、WM_PAINT、WM_ERASEBKGND等。 0X00A0----0X00A9:非客户区消息,例如WM_NCMOUSEMOVE、WM_NCLBUTTONDBLCLK、WM_NCRBUTTONUP等。 0X0100----0X0108:键盘消息,例如WM_KEYDOWN、WM_KEYFIRST、WM_CHAR、WM_SYSCHAR等。 0X0111----0X0112:菜单命令,例如WM_COMMAND、WM_SYSCOMMAND。0X0132----0x0132----0x0138:颜色控制消息,例如WM_CTLCOLORMSGBOX、WM_CTLCOLORLISTBOX、WM_CTLCOLORDLG等。 0X0200----0X020A:鼠标消息,例如WM_LBUTTONUP、WM_RBUTTONUP等。 0X0211----0X0213:菜单循环消息,例如WM_ENTERMENULOOP、WM_EXITMENULOOP等。 0X0220----0X0230:多文档消息,例如WM_MDICREATE、WM_MDIDESTROY等。 0X03E0----0X03E8:DDE消息,例如WM_PASTE、WM_CLEAR、WM_UNDO等。 0X0400:用户自定义消息的起点,WM_USER,以WM_USER + N形式出现。 0X8000:用户自定义消息的起点,WM_APP,以WM_APP + N形式出现。 0X0400----0X7FFF:用户自定义消息范围。 0X8001----0XBFFF,0xC000----0xFFFE:用户自定义消息范围。

原创粉丝点击