windows编程入门之消息

自己写了几遍要不是sql出现错误要不就是浏览器出问题,所以自己转载了一篇相关的文章。
二、 Windows 消息机制的概念
1 DOS Windows 驱动机制的区别
1 DOS 是过程驱动的。
传统的 MS-DOS 程序主要采用顺序的。关联的、过程驱动的程序设计方法。一个过程是一系列预先定义好的操作序列的组合,它具有一定的开头、中间过程和结束。程序直接控制程序事件和过程的顺序。这样的程序设计方法是面向程序而不是面向用户的,交互性差,用户界面不够友好,因为它强迫用户按照某种不可更改的模式进行工作。它的基本模型如图 1.1 所示。
2 Windows 是事件(消息)驱动
事件驱动程序设计是一种全新的程序设计方法,它不是由事件的顺序来控制,而是由事件的发生来控制,而这种事件的发生是随机的、不确定的,并没有预定的顺序,这样就允许程序的的用户用各种合理的顺序来安排程序的流程。对于需要用户交互的应用程序来说,事件驱动的程序设计有着过程驱动方法无法替代的优点。它是一种面向用户的程序设计方法,它在程序设计过程中除了完成所需功能之外,更多的考虑了用户可能的各种输入,并针对性的设计相应的处理程序。它是一种 被动 式程序设计方法,程序开始运行时,处于等待用户输入事件状态,然后取得事件并作出相应反应,处理完毕又返回并处于等待事件状态。它的框图如图 1.2 所示 :
 
2 、消息
Windows 系统是一个事件驱动的 OS ,每一个事件的发生都会产生一个消息,我们通过消息来知道发生了什么事件,了解事件,进而解决事件。 什么是消息呢?很难下一个定义,下面从不同的几个方面讲解一下:
   1) 消息的组成:一个消息由一个消息名称( UINT ),和两个参数( WPARAM LPARAM )。当用户进行了输入或是窗口的状态发生改变时系统都会发送消息到某一个窗口。例如当菜单转中之后会有 WM_COMMAND 消息发送, WPARAM 的高字中( HIWORD(wParam) )是命令的 ID 号,对菜单来讲就是菜单 ID 。当然用户也可以定义自己的消息名称,也可以利用自定义消息来发送通知和传送数据。
   2 )谁将收到消息:一个消息必须由一个窗口接收。在窗口的过程( WNDPROC )中可以对消息进行分析,对自己感兴趣的消息进行处理。例如你希望对菜单选择进行处理那么你可以定义对 WM_COMMAND 进行处理的代码,如果希望在窗口中进行图形输出就必须对 WM_PAINT 进行处理。
   3
)未处理的消息到那里去了: M$ 为窗口编写了默认的窗口过程,这个窗口过程将负责处理那些你不处理消息。正因为有了这个默认窗口过程我们才可以利用 Windows 的窗口进行开发而不必过多关注窗口各种消息的处理。例如窗口在被拖动时会有很多消息发送,而我们都可以不予理睬让系统自己去处理。
   4 )窗口句柄:说到消息就不能不说窗口句柄,系统通过窗口句柄来在整个系统中唯一标识一个窗口,发送一个消息时必须指定一个窗口句柄表明该消息由那个窗口接收。而每个窗口都会有自己的窗口过程,所以用户的输入就会被正确的处理。例如有两个窗口共用一个窗口过程代码,你在窗口一上按下鼠标时消息就会通过窗口一的句柄被发送到窗口一而不是窗口二。
3 、消息的来源
事件驱动围绕着消息的产生与处理展开,一条消息是关于发生的事件的消息。事件驱动是靠消息循环机制来实现的。也可以理解为消息是一种报告有关事件发生的通知。
Windows 应用程序的消息来源有一下四种:
1 )输入消息:包括键盘和鼠标的输入。 这一类消息首先放在系统消息队列中,然后由 Windows 将它们送入应用程序消息队列中,由应用程序来处理消息。
2 )控制消息:用来与 Windows 的控制对象,如列表框、按钮、检查框等进行双向通信。当用户在列表框中改动当前选择或改变了检查框的状态时发出此类消息。这类消息一般不经过应用程序消息队列,而是直接发送到控制对象上去。
3 )系统消息:对程序化的事件或系统时钟中断作出反应。一些系统消息,象 DDE 消息(动态数据交换消息)要通过 Windows 的系统消息队列,而有的则不通过系统消息队列而直接送入应用程序的消息队列,如创建窗口消息。
  4 )用户消息:这是程序员自己定义并在应用程序中主动发出的,一般由应用程序的某一部分内部处理。
4 Windows 的消息系统的组成
Windows 的消息系统由以下 3 部分组成:
消息队列: Windows 能够为所有的应用程序维护一个消息队列,应用程序必须从消息队列中获去消息,然后分派给某个窗体。
消息循环: 通过这个循环机制,应用程序从消息队列中检索消息,再把它分派给适当的窗口,然后继续从消息队列中检索下一条消息,再分派给适当的窗口,依次进行。
窗口过程:每个窗口都有一个窗口过程,以接收 Windows 传递给窗口的消息,窗口过程的任务就是获取消息并且响应它。窗口过程是一个回调函数,处理完一个消息后,通常要给 Windows 一个返回值。
5 、消息的响应
消息的产生来源于系统事情 ( 包括计时器事件 ) 和用户事情 ,Windows 用消息来调入和关闭 ( 还有其它处理 , 如绘制一个窗口等 ) 应用程序 , 一个典型表现是在关机操作中, Windows 发一个关机的消息给所有正在运行的应用程序,告知它们退出内存,此时,应用程序用回应消息的方法来响应 OS ,因此,消息是应用程序与 WinOS 交互的手段 ..
消息产生到被窗口响应的步骤:(如下图)
1>     系统发生了或用户发出某个事件。
2>     Windows 把这个事件翻译为消息,然后把他放到消息队列中
3>     应用程序从消息队列中接受到这个消息,把他存放到 TMsg 记录中。
4>     应用程序把消息传递给一个适当的窗体过程。
窗体过程响应这个消息并进行处理。把消息传递给了这个窗体的窗体函数。
三、 Windows 消息机制要点
1. 窗口过程
每个窗口会有一个称为窗口过程的回调函数 (WndProc) ,它带有四个参数,分别为:窗口句柄 (Window Handle), 消息 ID(Message ID), 和两个消息参数 (wParam, lParam), 当窗口收到消息时系统就会调用此窗口过程来处理消息。(所以叫回调函数)
2 消息类型
1) 系统定义消息 (System-Defined Messages)
SDK 中事先定义好的消息,非用户定义的,其范围在 [0×0000, 0×03ff] 之间, 可以分为以下三类:
窗口消息 (Windows Message)
与窗口的内部运作有关,如创建窗口,绘制窗口,销毁窗口等。可以是一般的窗口,可以是         Dialog, 控件等。
如: WM_CREATE, WM_PAINT, WM_MOUSEMOVE, WM_CTLCOLOR, WM_HSCROLL..
命令消息 (Command Message)
与处理用户请求有关, 如单击菜单项或工具栏或控件时, 就会产生命令消息。
WM_COMMAND, LOWORD(wParam) 表示菜单项,工具栏按钮或控件的 ID 。如果是控件 , HIWORD(wParam) 表示控件消息类型
  控件通知 (Notify Message)
控件通知消息, 这是最灵活的消息格式, Message, wParam, lParam 分别为: WM_NOTIFY, 控件 ID ,指向 NMHDR 的指针。 NMHDR 包含控件通知的内容, 可以任意扩展。
2) 程序定义消息 (Application-Defined Messages)
用户自定义的消息, 对于其范围有如下规定:
 WM_USER: 0×0400-0×7FFF    (ex. WM_USER+10)
 WM_APP(winver>4.0): 0×8000-0xBFFF (ex.WM_APP+4)
RegisterWindowMessage: 0xC000-0xFFFF
3 消息队列 (Message Queues)
Windows 中有两种类型的消息队列
 1) 系统消息队列 (System Message Queue)
这是一个系统唯一的 Queue ,设备驱动 (mouse, keyboard) 会把操作输入转化成消息存在系统队列中,然后系统会把此消息放到目标窗口所在的线程的消息队列 (thread-specific message queue) 中等待处理
2) 线程消息队列 (Thread-specific Message Queue)
每一个 GUI 线程都会维护这样一个线程消息队列。 ( 这个队列只有在线程调用 GDI 函数时才会创建,默认不创建 ) 。然后线程消息队列中的消息会被送到相应的窗口过程 (WndProc) 处理 .
注意: 线程消息队列中 WM_PAINT WM_TIMER 只有在 Queue 中没有其他消息的时候才会被处理, WM_PAINT 消息还会被合并以提高效率。其他所有消息以先进先出( FIFO )的方式被处理。
4 队列消息 (Queued Messages) 和非队列消息 (Non-Queued Messages)
1) 队列消息 (Queued Messages)
消息会先保存在消息队列中,消息循环会从此队列中取消息并分发到各窗口处理
如鼠标,键盘消息。
2) 非队列消息 (NonQueued Messages)
消息会绕过系统消息队列和线程消息队列直接发送到窗口过程被处理
如: WM_ACTIVATE, WM_SETFOCUS, WM_SETCURSOR WM_WINDOWPOSCHANGED
注意 : postMessage 发送的消息是队列消息,它会把消息 Post 到消息队列中; SendMessage 发送的消息是非队列消息, 被直接送到窗口过程处理
5 Windows 消息函数
1 PostMessage(PostThreadMessage), SendMessage
PostMessage: 把消息放到指定窗口所在的线程消息队列中后立即返回。 PostThreadMessage :把消息放到指定线程的消息队列中后立即返回。
SendMessage :直接把消息送到窗口过程处理, 处理完了才返回。
2 GetMessage, PeekMessage
PeekMessage 会立即返回    可以保留消息
GetMessage 在有消息时返回   会删除消息
3 TranslateMessage, TranslateAccelerator
TranslateMessage: 把一个 virtual-key 消息转化成字符消息 (character message) ,并放到当前线程的消息队列中,消息循环下一次取出处理。
TranslateAccelerator: 将快捷键对应到相应的菜单命令。它会把 WM_KEYDOWN WM_SYSKEYDOWN 转化成快捷键表中相应的 WM_COMMAND WM_SYSCOMMAND 消息, 然后把转化后的 WM_COMMAND WM_SYSCOMMAND 直接发送到窗口过程处理, 处理完后才会返回。
6 消息死锁 ( Message Deadlocks
假设有线程 A B 现在有以下下步骤
1 线程 A SendMessage 给线程 B, A 等待消息在线程 B 中处理后返回
2 )线程 B 收到了线程 A 发来的消息,并进行处理, 在处理过程中, B 也向线程 A SendMessgae ,然后等待从 A 返回。
因为此时, 线程 A 正等待从线程 B 返回, 无法处理 B 发来的消息, 从而导致了 \ 线程 A,B 相互等待, 形成死锁。多个线程也可以形成环形死锁。
可以使用 SendNotifyMessage SendMessageTimeout 来避免出现死锁。
7 BroadcastSystemMessage
我们一般所接触到的消息都是发送给窗口的, 其实, 消息的接收者可以是多种多样的,它可以是应用程序 (applications), 可安装驱动 (installable drivers), 网络设备 (network drivers), 系统级设备驱动 (system-level device drivers) 等,
BroadcastSystemMessage 这个 API 可以对以上系统组件发送消息。
 
那么这些消息是怎样传送的呢。我们以 MFC为例来看一下消息传送过程。
四、MFC消息机制
Windows应用程序的主函数中,首先要注册窗口类,然后创建并显示窗口。创建窗口后程序就进入消息循环,在消息循环中,程序不断地获得消息并将消息派送给对应的窗口函数进行处理。
我们可以看到,在 MFC 的框架结构下,可以进行消息处理的类的头文件里面
都会含有 DECLARE_MESSAGE_MAP() , 这里主要进行消息映射和消息处理函数的声
明。可以进行消息处理的类的实现文件里一般都含有如下的结构。
BEGIN_MESSAGE_MAP(CInheritClass, CBaseClass) //{{AFX_MSG_MAP(CInheritClass)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
这里主要进行消息映射的实现和消息处理函数的实现。
  所有能够进行消息处理的类都是基于 CCmdTarget 类的,也就是说 CCmdTarget
类是所有可以进行消息处理类的父类。 CCmdTarget 类是 MFC 处理命令消息的基础和核心。  
同时 MFC 定义了下面的两个主要结构
AFX_MSGMAP_ENTRY
struct AFX_MSGMAP_ENTRY
{//“““//
};
AFX_MSGMAP
struct AFX_MSGMAP
{//“““`//
};
    其中 AFX_MSGMAP_ENTRY 结构包含了一个消息的所有相关信息 , AFX_MSGMAP 主要作用是两个,一:用来得到基类的消息映射入口地址。二:得到本身的消息映射入口地址。  
实际上, MFC 把所有的消息一条条填入到 AFX_MSGMAP_ENTRY 结构中去,形成一个数组,该数组存放了所有的消息和与它们相关的消息的参数。同时通过 AFX_MSGMAP 能得到该数组的首地址,同时也得到基类的消息映射入口地址,这是为例当本身对该消息不响应的时候,就调用其基类的消息响应。
现在我们来分析MFC是如何让窗口过程来处理消息的,实际上所有MFC的窗口类都通过钩子函数_AfxCbtFilterHook截获消息,并且在钩子函数_AfxCbtFilterHook中把窗口过程设定为AfxWndProc。原来的窗口过程保存在成员变量m_pfnSuper
1.MFC 框架下,接收处理来自 Windows 消息的过程:
 
 
2.MFC内部消息处理方式
MFC 接收一个寄送的消息:
MFC 处理一个寄送和发送消息的惟一明显不同是寄送的消息要做应用程序的消息队列中花费一些时间。在消息泵( message pump )弹出它之前,它要一直在队列中。下面是怎样接受寄送消息的过程。 MFC 应用程序中的消息泵在 CWinApp 的成员函数 Run ()中。 应用程序开始运行时, Run() 就被调用 ,Run() 把时间分割成两部分。一部分用来执行后台处理,如取消临时 CWnd 对象;另一部分用来检查消息队列。当一个新的消息进来时, Run() 抽取它 即用 GetMessage( ) 从队列中取出该消息,运行 PreTranslateMessage( ) ::TranslateMessage( ) 两个消息翻译函数,然后用 DispatchMessage( ) 函数调用该消息预期的目标窗口进程。如下图。
我们用一个实例函数 A 发送消息到函数 B 的过程来看一下 MFC 内部消息处理过程。
1.                          首先函数 A 应获取消息的 CWnd 类对象的指针,然后,调用 CWnd 的成员函数 SendMessage ()。
LRESULT Res=pWnd->SendMessage(UINT Msg, WPARAM wParam, LPARAM lParam);
   pWnd 指针指向目标 CWnd 类对象。变量 Msg 是消息, wParam lParam 变量包含消息的参数,如鼠标单单击哪里或选择了什么菜单项。目标窗口返回的消息结果放在变量 Res 中。
发送消息到一个没有 CWnd 的函数对象的窗口,可以用下列目标窗口的句柄直接调用 Windows API
LRESULT Res=::SendMessage(HWND hWnd, UINT Msg,  WPARAM wParam, LPARAM lParam);
     
这里的 hWnd 是目标窗口的句柄。
如果是异步传输也可使用 PostMessage (),消息同上相同,但返回值 Res 不一样, Res 不是一个由目标窗体返回的值,而是一个布尔值,用来表示消息是否成功的放到消息队列中。
 
2.   正常情况下,一旦消息被发送后,应用程序后台会自动的将它发送, 但是在特殊情况下,需要你自己去删除一个消息,例如想在应用程序接收到某种消息之前停止应用程序。有两种方法可以从应用程序消息队列中删除一个消息,但这两种方法都没有涉及 MFC
第一种方法:在不干扰任何事情之下窥视消息队列,看看一个消息是否在那里。
    BOOL res=::PeekMessage(LPMSG lpMsg, HWND hWnd, UINT wMsFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg ) ;
   第二种方法:实际上是等待,一直等到一个新的消息到达队列为止,然后删除并返回该消息。
    BOOL res=::GetMessage(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax);
    
在这两种方法中,变量 hWnd 指定要截获消息的窗口,如果该变量设为 NULL ,所有窗口消息将被截获。 wMsgFilterMin wMsgFilterMax 变量与 SendMessage( ) 中的变量 Msg 相对应,指定查看消息的范围。如果用 “0,0″ ,则所有的消息都将被截获。如果用 WM_KEYFIRST,WM_KEYLAST WM_MOUSEFIRST,WM_MOUSELAST ,则所有键盘或鼠标的消息将被截获。 wRemoveMsg 变量指定 PeekMessage( ) 是否应该真正地从队列中删除该消息。 (GetMessage( ) 总是删除消息 ) 。该变量可以取两个值:
   
   PM_REMOVE PeekMessage( ) 将删除消息。
   
   PM_NOREMOVE PeekMessage( ) 将把消息留在队列里,并返回它的一个拷贝。
    
当然,如果把消息留在消息队列中,然后再次调用 PeekMessage( ) 查看相同类型的消息,则将返回完全相同的消息。
    lpMsg
变量是一个指向 MSG 结构的指针, MSG 包含检索到的消息。
    typedef struct tagMSG {
                        HWND hwnd; // window handle message is intended for
                        UINT message;
                        WPARAM wParam;
                        LPARAM lParam;
                        DWORD time; // the time the message was put in the queue
                        POINT pt; // the location of the mouse cursor when the
                                       // message was put in the queue
                        } MSG;
3.   消息会到消息队列中。 CwinApp 的成员函数 Run ,在应用程序运行时, Run 就把时间分割成两部分,一部分执行后台的处理,另一部分来检查消息的队列,当发现新消息时, Run 就调用 GetMessage ()从队列消息中取出该消息。
3. 运行两个消息翻译函数 PreTranslateMessage ()和:: TranslateMessage (),进行翻译。主要找到函数对象的位置、消息的动作标识和跟消息相关的执行操作。
4. DispatchMessage ()函数调用该消息预期的函数 B 进程,进行执行。
 
 


本文地址: [url]http://read.newbooks.com.cn/info/171116.html[/url]

你可能感兴趣的:(入门,职场,休闲,Windows编程)