精华内容
下载资源
问答
  • Windows窗口消息大全

    热门讨论 2013-05-24 11:16:10
    Windows窗口消息大全,全不全自己看
  • windows消息传递机制详解

    千次阅读 多人点赞 2018-01-31 18:06:11
    Windows消息提供了应用程序之间、应用程序与Windows系统之间进行通信的手段。应用程序想要实现的功能由消息来触发,并且靠对消息的响应和处理来完成。必须注意的是,消息并非是抢占性的,无论事件的缓急,总是按照...
                
    林炳文Evankaka
    原创作品。转载请注明出处
    http://blog.csdn.net/evankaka 
    

         Windows是一个消息(Message)驱动系统。Windows的消息提供了应用程序之间、应用程序与Windows系统之间进行通信的手段。应用程序想要实现的功能由消息来触发,并且靠对消息的响应和处理来完成。必须注意的是,消息并非是抢占性的,无论事件的缓急,总是按照到达的先后派对,依次处理(一些系统消息除外),这样可能使一些实时外部事件得不到及时处理。

          Windows的应用程序一般包含窗口(Window),它主要为用户提供一种可视化的交互方式,窗口是总是在某个线程(Thread)内创建的。Windows系统通过消息机制来管理交互,消息(Message)被发送,保存,处理,一个线程会维护自己的一套消息队列(Message Queue),以保持线程间的独占性。队列的特点无非是先进先出,这种机制可以实现一种异步的需求响应过程。

    目录:

    1、消息

    2  、消息类型

    3 、消息队列(Message Queues)

    4 、队列消息(Queued Messages)和非队列消息(Non-Queued Messages)

    5 、PostMessage(PostThreadMessage), SendMessage

    6 、GetMessage, PeekMessage

    7 、TranslateMessage, TranslateAccelerator

    8、(消息死锁( Message Deadlocks)

    9、BroadcastSystemMessage

    10、消息的处理

    11、MFC的消息映射

    12、消息反射机制


    1、消息

        消息系统对于一个win32程序来说十分重要,它是一个程序运行的动力源泉。一个消息,是系统定义的一个32位的值,他唯一的定义了一个事件,向Windows发出一个通知,告诉应用程序某个事情发生了。例如,单击鼠标、改变窗口尺寸、按下键盘上的一个键

    都会使Windows发送一个消息给应用程序。

        消息本身是作为一个记录传递给应用程序的,这个记录中包含了消息的类型以及其他信息。例如,对于单击鼠标所产生的消息来

    说,这个记录中包含了单击鼠标时的坐标。这个记录类型叫做MSG,MSG含有来自windows应用程序消息队列的消息信息,它在

    Windows中声明如下: 
    typedef struct tagMsg 

    HWND hwnd;          // 接受该消息的窗口句柄 
    UINT message;         // 消息常量标识符,也就是我们通常所说的消息号 
    WPARAM wParam;     // 32位消息的特定附加信息,确切含义依赖于消息值 
    LPARAM lParam;       // 32位消息的特定附加信息,确切含义依赖于消息值 
    DWORD time;         // 消息创建时的时间 
    POINT pt;             // 消息创建时的鼠标/光标在屏幕坐标系中的位置 
    }MSG; 
        消息可以由系统或者应用程序产生。系统在发生输入事件时产生消息。举个例子, 当用户敲键, 移动鼠标或者单击控件。系统也
    产生消息以响应由应用程序带来的变化, 比如应用程序改变系统字体,改变窗体大小。应用程序可以产生消息使窗体执行任务,或者与其他应用程序中的窗口通讯。

    2、消息类型

    1) 系统定义消息(System-Defined Messages)
            在SDK中事先定义好的消息,非用户定义的,其范围在[0x0000, 0x03ff]之间, 可以分为以下三类: 
    1> 窗口消息(Windows Message)
           与窗口的内部运作有关,如创建窗口,绘制窗口,销毁窗口等。可以是一般的窗口,也可以是Dialog,控件等。 
    如:WM_CREATE, WM_PAINT, WM_MOUSEMOVE, WM_CTLCOLOR, WM_HSCROLL... 
    2> 命令消息(Command Message)
            与处理用户请求有关, 如单击菜单项或工具栏或控件时, 就会产生命令消息。 
    WM_COMMAND, LOWORD(wParam)表示菜单项,工具栏按钮或控件的ID。如果是控件, HIWORD(wParam)表示控件消息类型 
    3> 控件通知(Notify Message)
            控件通知消息, 这是最灵活的消息格式, 其Message, wParam, lParam分别为:WM_NOTIFY, 控件ID,指向NMHDR的指针。NMHDR包含控件通知的内容, 可以任意扩展。 
    2) 程序定义消息(Application-Defined Messages)
            用户自定义的消息, 对于其范围有如下规定: 
    WM_USER: 0x0400-0x7FFF      (ex. WM_USER+10) 
    WM_APP(winver> 4.0): 0x8000-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发送的消息是非队列消息, 被直接送到窗口过程处理.

    队列消息和非队列消息的区别
            从消息的发送途径来看,消息可以分成2种:队列消息和非队列消息。消息队列由可以分成系统消息队列和线程消息队列。系统消息队列由Windows维护,线程消息队列则由每个GUI线程自己进行维护,为避免给non-GUI现成创建消息队列,所有线程产生时并没有消息队列,仅当线程第一次调用GDI函数时系统才给线程创建一个消息队列。队列消息送到系统消息队列,然后到线程消息队列;非队列消息直接送给目的窗口过程。
         对于队列消息,最常见的是鼠标和键盘触发的消息,例如WM_MOUSERMOVE,WM_CHAR等消息,还有一些其它的消息,例如:WM_PAINT、 WM_TIMER和WM_QUIT。当鼠标、键盘事件被触发后,相应的鼠标或键盘驱动程序就会把这些事件转换成相应的消息,然后输送到系统消息队列,由 Windows系统去进行处理。Windows系统则在适当的时机,从系统消息队列中取出一个消息,根据前面我们所说的MSG消息结构确定消息是要被送往那个窗口,然后把取出的消息送往创建窗口的线程的相应队列,下面的事情就该由线程消息队列操心了,Windows开始忙自己的事情去了。线程看到自己的消息队列中有消息,就从队列中取出来,通过操作系统发送到合适的窗口过程去处理。
         一般来讲,系统总是将消息Post在消息队列的末尾。这样保证窗口以先进先出的顺序接受消息。然而,WM_PAINT是一个例外,同一个窗口的多个 WM_PAINT被合并成一个 WM_PAINT 消息, 合并所有的无效区域到一个无效区域。合并WM_PAIN的目的是为了减少刷新窗口的次数。

          非队列消息将会绕过系统队列和消息队列,直接将消息发送到窗口过程,。系统发送非队列消息通知窗口,系统发送消息通知窗口。例如,当用户激活一个窗口系统发送WM_ACTIVATE, WM_SETFOCUS, and WM_SETCURSOR。这些消息通知窗口它被激活了。非队列消息也可以由当应用程序调用系统函数产生。例如,当程序调用SetWindowPos系统发送WM_WINDOWPOSCHANGED消息。一些函数也发送非队列消息,例如下面我们要谈到的函数。

    5 、PostMessage(PostThreadMessage), SendMessage
            PostMessage:把消息放到指定窗口所在的线程消息队列中后立即返回。 PostThreadMessage:把消息放到指定线程的消息队列中后立即返回。 
            SendMessage:直接把消息送到窗口过程处理, 处理完了才返回。

    PostMessage(异步)和SendMessage(同步)的区别

    a、 PostMessage 是异步的,SendMessage 是同步的。

             PostMessage 只把消息放到队列,不管消息是不是被处理就返回,消息可能不被处理;

            SendMessage等待消息被处理完了才返回,如果消息不被处理,发送消息的线程将一直处于阻塞状态,等待消息的返回。

    b、 同一个线程内:

              SendMessage 发送消息时,由USER32.DLL模块调用目标窗口的消息处理程序,并将结果返回,SendMessage 在同一个线程里面发送消息不进入线程消息队列;PostMessage 发送的消息要先放到消息队列,然后通过消息循环分派到目标窗口(DispatchMessage)。

    c、不同线程:

                 SendMessage 发送消息到目标窗口的消息队列,然后发送消息的线程在USER32。DLL模块内监视和等待消息的处理结果,直到目标窗口的才处理返回,SendMessage在返回之前还需要做许多工作,如响应别的线程向它发送的SendMessage().PostMessge() 到别的线程的时候最好使用PostThreadMessage   代替。PostMessage()的HWND 参数可以为NULL,相当于PostThreadMessage() + GetCrrentThreadId.

    d、系统处理消息。

           系统只处理(marshal)系统消息(0--WM_USER),发送用户消息(用户自己定义)时需要用户自己处理。

            使用PostMessage,SendNotifyMessage,SendMessageCallback等异步函数发送系统消息时,参数不可以使用指针,因为发送者不等待消息的处理就返回,接收者还没有处理,指针就有可能被释放了,或则内容变化了。

    e、在Windows 2000/XP,每个消息队列最多只能存放一定数量的消息,超过的将不会被处理就丢掉。系统默认是10000;:[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows] USERPostMessageLimit

    6 、GetMessage, PeekMessage
    PeekMessage会立即返回    可以保留消息 
    GetMessage在有消息时返回  会删除消息

    PeekMessage和GetMessage函数的主要区别有:
            a. GetMessage的主要功能是从消息队列中“取出”消息,消息被取出以后,就从消息队列中将其删除;而PeekMessage的主要功能是“窥视”消息,如果有消息,就返回true,否则返回false。也可以使用PeekMessage从消息队列中取出消息,这要用到它的一个参数(UINT wRemoveMsg),如果设置为PM_REMOVE,消息则被取出并从消息队列中删除;如果设置为PM_NOREMOVE,消息就不会从消息队列中取出。
            b. 如果GetMessage从消息队列中取不到消息,则线程就会被操作系统挂起,等到OS重新调度该线程时,两者的性质不同:使用GetMessage线程仍会被挂起,使用PeekMessage线程会得到CPU的控制权,运行一段时间。
            c、GetMessage每次都会等待消息,直到取到消息才返回;而PeekMessage只是查询消息队列,没有消息就立即返回,从返回值判断是否取到了消息。
    我们也可以说,PeekMessage是一个具有线程异步行为的函数,不管消息队列中是否有消息,函数都会立即返回。而GetMessage则是一个具有线程同步行为的函数,如果消息队列中没有消息的话,函数就会一直等待,直到消息队列中至少有一条消息时才返回。
    如果消息队列中没有消息,PeekMessage总是能返回,这就相当于在执行一个循环,如果消息队列一直为空, 它就进入了一个死循环。GetMessage则不可能因为消息队列为空而进入死循环。

    联系:

            在Windows的内部,GetMessage和PeekMessage执行着相同的代码,Peekmessage和Getmessage都是向系统的消息队列中取得消息,并将其放置在指定的结构。

    区别:

    PeekMessage:有消息时返回TRUE,没有消息返回FALSE 

    GetMessage:有消息时且消息不为WM_QUIT时返回TRUE,如果有消息且为WM_QUIT则返回FALSE,没有消息时不返回。

    GetMessage:取得消息后,删除除WM_PAINT消息以外的消息。

    PeekMessage:取得消息后,根据wRemoveMsg参数判断是否删除消息。PM_REMOVE则删除,PM_NOREMOVE不删除。

    The PeekMessage function normally does not remove WM_PAINT messages from the queue. WM_PAINT messages remain in the queue until they are processed. However, if a WM_PAINT message has a null update region, PeekMessage does remove it from the queue.

    不能用PeekMessage从消息队列中删除WM_PAINT消息,从队列中删除WM_PAINT消息可以令窗口显示区域的失效区域变得有效(刷新窗口),如果队列中包含WM_PAINT消息程序就会一直while循环了。

    7 、TranslateMessage, TranslateAccelerator
             TranslateMessage: 把一个virtual-key消息转化成字符消息(character message),并放到当前线程的消息队列中,消息循环下一次取出处理。 
            TranslateAccelerator: 将快捷键对应到相应的菜单命令。它会把WM_KEYDOWN 或 WM_SYSKEYDOWN转化成快捷键表中相应的WM_COMMAND 或WM_SYSCOMMAND消息, 然后把转化后的 WM_COMMAND或WM_SYSCOMMAND直接发送到窗口过程处理, 处理完后才会返回。

    8、(消息死锁( Message Deadlocks)
    假设有线程A和B, 现在有以下下步骤 
            1) 线程A SendMessage给线程B, A等待消息在线程B中处理后返回 
            2) 线程B收到了线程A发来的消息,并进行处理, 在处理过程中,B也向线程A SendMessgae,然后等待从A返回。  因为此时, 线程A正等待从线程B返回, 无法处理B发来的消息, 从而导致了线程A,B相互等待, 形成死锁。多个线程也可以形成环形死锁。 
    可以使用 SendNotifyMessage或SendMessageTimeout来避免出现死锁。

    9、BroadcastSystemMessage
            我们一般所接触到的消息都是发送给窗口的, 其实, 消息的接收者可以是多种多样的,它可以是应用程序(applications), 可安装驱动(installable drivers), 网络设备(network drivers), 系统级设备驱动(system-level device drivers)等, 
    BroadcastSystemMessage这个API可以对以上系统组件发送消息。

    10、消息的处理
            接下来我们谈一下消息的处理,首先我们来看一下VC中的消息泵:

    while(GetMessage(&msg, NULL, 0, 0))
    {
           if(!TranslateAccelerator(msg.hWnd, hAccelTable, &msg))

                TranslateMessage(&msg);
                DispatchMessage(&msg);
           }
    }

    TranslateMessage(转换消息):

            用来把虚拟键消息转换为字符消息。由于Windows对所有键盘编码都是采用虚拟键的定义,这样当按键按下时,并不得字符消息,需要键盘映射转换为字符的消息。

    TranslateMessage函数

            用于将虚拟键消息转换为字符消息。字符消息被投递到调用线程的消息队列中,当下一次调用GetMessage函数时被取出。当我们敲击键盘上的某个字符键时,系统将产生WM_KEYDOWN和WM_KEYUP消息。这两个消息的附加参数(wParam和lParam)包含的是虚拟键代码和扫描码等信息,而我们在程序中往往需要得到某个字符的ASCII码,TranslateMessage这个函数就可以将WM_KEYDOWN和WM_ KEYUP消息的组合转换为一条WM_CHAR消息(该消息的wParam附加参数包含了字符的ASCII码),并将转换后的新消息投递到调用线程的消息队列中。注意,TranslateMessage函数并不会修改原有的消息,它只是产生新的消息并投递到消息队列中。

    也就是说TranslateMessage会发现消息里是否有字符键的消息,如果有字符键的消息,就会产生WM_CHAR消息,如果没有就会产生什么消息。

    DispatchMessage(分派消息):

    把 TranslateMessage转换的消息发送到窗口的消息处理函数,此函数在窗口注册时已经指定。

            首先,GetMessage从进程的主线程的消息队列中获取一个消息并将它复制到MSG结构,如果队列中没有消息,则GetMessage函数将等待一个消息的到来以后才返回。如果你将一个窗口句柄作为第二个参数传入GetMessage,那么只有指定窗口的的消息可以从队列中获得。GetMessage也可以从消息队列中过滤消息只接受消息队列中落在范围内的消息。这时候就要利用GetMessage/PeekMessage指定一个消息过滤器。这个过滤器是一个消息标识符的范围或者是一个窗体句柄,或者两者同时指定。当应用程序要查找一个后入消息队列的消息是很有用。WM_KEYFIRST 和 WM_KEYLAST 常量用于接受所有的键盘消息。 WM_MOUSEFIRST 和 WM_MOUSELAST 常量用于接受所有的鼠标消息。
    然后TranslateAccelerator判断该消息是不是一个按键消息并且是一个加速键消息,如果是,则该函数将把几个按键消息转换成一个加速键消息传递给窗口的回调函数。处理了加速键之后,函数TranslateMessage将把两个按键消息WM_KEYDOWN和WM_KEYUP转换成一个 WM_CHAR,不过需要注意的是,消息WM_KEYDOWN,WM_KEYUP仍然将传递给窗口的回调函数。    
    处理完之后,DispatchMessage函数将把此消息发送给该消息指定的窗口中已设定的回调函数。如果消息是WM_QUIT,则 GetMessage返回0,从而退出循环体。应用程序可以使用PostQuitMessage来结束自己的消息循环。通常在主窗口的 WM_DESTROY消息中调用。

    11、MFC的消息映射
             使用MFC编程时,消息发送和处理的本质和Win32相同,但是,它对消息处理进行了封装,简化了程序员编程时消息处理的复杂性,它通过消息映射机制来处理消息,程序员不必去设计和实现自己的窗口过程。
    说白了,MFC中的消息映射机制实质是一张巨大的消息及其处理函数对应表。消息映射基本上分为两大部分:
    在头文件(.h)中有一个宏DECLARE_MESSAGE_MAP(),它放在类的末尾,是一个public属性的;与之对应的是在实现部分(.cpp)增加了一个消息映射表,内容如下:
    BEGIN_MASSAGE_MAP(当前类,当前类的基类)
    //{{AFX_MSG_MAP(CMainFrame)
    消息的入口项
    //}}AFX_MSG_MAP
    END_MESSAGE_MAP()
    但是仅是这两项还不足以完成一条消息,要是一个消息工作,必须还有以下3个部分去协作:
    1、在类的定义中加入相应的函数声明;
    2、在类的消息映射表中加入相应的消息映射入口项;
    3、在类的实现中加入相应的函数体;
    消息的添加
    (1)、利用Class Wizard实现自动添加
            在菜单中选择View -> Class Wizard激活Class Wizard,选择Message Map标签,从Class name组合框中选取我们想要添加消息的类。在Object IDs列表框中,选取类的名称。此时,Messages列表框显示该类的可重载成员函数和窗口消息。可重载成员函数显示在列表的上部,以实际虚构成员函数的大小写字母来表示。其他为窗口消息,以大写字母出现。选中我们要添加的消息,单击Add Funtion按钮,Class Wizard自动将该消息添加进来。
    有时候,我们想要添加的消息在Message列表中找不到,我们可以利用Class Wizard上Class Info标签以扩展消息列表。在该页中,找到Message Filter组合框,通过它可以改变首页中Messages列表框中的选项。
    (2)、手动添加消息
     如果Messages列表框中确实没有我们想要的消息,就需要我们手工添加:
            1)在类的.h文件中添加处理函数的声明,紧接着在//}}AFX_MSG行之后加入声明,注意,一定要以afx_msg开头。
    通常,添加处理函数声明的最好的地方是源代码中Class Wizard维护的表的下面,在它标记其领域的{{ }}括弧外面。这些括弧中的任何东西都有可能会被Class Wizard销毁。
            2)接着,在用户类的.cpp文件中找到//}}AFX_MSG_MAP行,紧接在它之后加入消息入口项。同样,也放在{{ }}外面。
            3)最后,在该文件中添加消息处理函数的实体。
    对于能够使用Class Wizard添加的消息,尽量使用Class Wizard添加,以减少我们的工作量;对于不能使用Class Wizard添加的消息和自定义消息,需要手动添加。总体说来,MFC的消息编程对用户来说,相对比较简单,在此不再使用实例演示。
            12、消息反射机制
    什么叫消息反射?
             父窗口将控件发给它的通知消息,反射回控件进行处理(即让控件处理这个消息),这种通知消息让控件自己处理的机制叫做消息反射机制。
            通过前面的学习我们知道,一般情况下,控件向父窗口发送通知消息,由父窗口处理这些通知消息。这样,父窗口(通常是一个对话框)会对这些消息进行处理,换句话说,控件的这些消息处理必须在父窗口类体内,每当我们添加子控件的时候,就要在父窗口类中复制这些代码。很明显,这对代码的维护和移植带来了不便,而且,明显背离C++的对象编程原则。
            从4.0版开始,MFC提供了一种消息反射机制(Message Reflection),可以把控件通知消息反射回控件。具体地讲,对于反射消息,如果控件有该消息的处理函数,那么就由控件自己处理该消息,如果控件不处理该消息,则框架会把该消息继续送给父窗口,这样父窗口继续处理该消息。可见,新的消息反射机制并不破坏原来的通知消息处理机制。
    消息反射机制为控件提供了处理通知消息的机会,这是很有用的。如果按传统的方法,由父窗口来处理这个消息,则加重了控件对象对父窗口的依赖程度,这显然违背了面向对象的原则。若由控件自己处理消息,则使得控件对象具有更大的独立性,大大方便了代码的维护和移植。
            实例M8:简单地演示MFC的消息反射机制。(见附带源码 工程M8)
         开VC++ 6.0,新建一个基于对话框的工程M8。
    在该工程中,新建一个CMyEdit类,基类是CEdit。接着,在该类中添加三个变量,如下:
    private:
    CBrush m_brBkgnd;
    COLORREF m_clrBkgnd;
    COLORREF m_clrText;
    在CMyEdit::CMyEdit()中,给这三个变量赋初值:
    {
    m_clrBkgnd = RGB( 255, 255, 0 );
    m_clrText = RGB( 0, 0, 0 );
    m_brBkgnd.CreateSolidBrush(RGB( 150, 150, 150) );
    }
    打开ClassWizard,类名为CMyEdit,Messages处选中“=WM_CTLCOLOR”,您是否发现,WM_CTLCOLOR消息前面有一个等号,它表示该消息是反射消息,也就是说,前面有等号的消息是可以反射的消息。
    消息反射函数代码如下:
    HBRUSH CMyEdit::CtlColor(CDC* pDC, UINT nCtlColor)
    {
        // TODO: Change any attributes of the DC here
        pDC->SetTextColor( m_clrText );//设置文本颜色
        pDC->SetBkColor( m_clrBkgnd );//设置背景颜色
         //请注意,在我们改写该函数的内容前,函数返回NULL,即return NULL;
        //函数返回NULL将会执行父窗口的CtlColor函数,而不执行控件的CtlColor函数
        //所以,我们让函数返回背景刷,而不返回NULL,目的就是为了实现消息反射
        return m_brBkgnd; //返回背景刷
    }
    在IDD_M8_DIALOG对话框中添加一个Edit控件,使用ClassWizard给该Edit控件添加一个CMyEdit类型的变量m_edit1,把Edit控件和CMyEdit关联起来。

    林炳文Evankaka原创作品。转载请注明出处http://blog.csdn.net/evankaka

    展开全文
  • Windows消息机制

    千次阅读 2018-09-04 23:22:17
    这个模式怎么区分消息属于谁,是通过一个专门进程处理消息,这样问题是跨进程通信太费时。Windows不是这样,Linux是这样。微软是把消息队列存到了0环。 怎么找到消息结构体,是KThread(任何一个线程都个内核...

    消息队列在哪

    消息队列

    进程-->消息队列
    消息队列-->鼠标
    消息队列-->键盘
    消息队列-->其他进程的消息

    消息队列放在哪里

    1

    这个模式怎么区分消息属于谁,是通过一个专门进程处理消息,这样问题是有跨进程通信太费时。Windows不是这样,Linux是这样。微软是把消息队列存到了0环。
    怎么找到消息结构体,是KThread(任何一个线程都有个内核结构体EThread,EThread第一个成员有是Kthread,Kthread有个结构体是Win32Thread,这个数据结构比较特殊,只有图形界面才有这个结构,这个结构体有消息队列)

    微软的解决方案:GUI线程

    <1> 当线程刚创建的时候,都是普通线程:
    Win32Thread是空的。

    graph LR
    Thread.ServiceTable-->KeServiceDescriptorTable

    <2> 当线程第一次调用Win32k.sys时,操作系统线程会调用一个函数:PsConvertToGuiThread
    主要做几件事:
    1. 扩充内核栈,必须换成64KB的大内核栈,因为普通内核栈只有12KB大小。
    2. 创建一个包含消息队列的结构体,并挂到KTHREAD上。(Win32Thread指向新结构体,里有个结构体ThreadInfo,里面有个变量是MessageQueue)
    3.
    ServiceTable服务表改变指向

    graph LR
    Thread.ServiceTable-->KeServiceDescriptorTableShadow
    1. 把需要的内存数据映射到本进程空间

    总结

    <1> 消息队列存储在0环,通过KTHREAD.Win32Thread可以找到

    <2> 并不是所有线程都要消息队列,只有GUI线程才有消息队列

    <3> 一个GUI线程对应1个消息队列

    窗口与线程的关系

    消息队列与线程的关系:一个GUI线程对应一个消息队列

    消息到哪里去

    去了当前窗口所对应的消息队列,

    win32wk启动时,会启动两个线程,监控鼠标,和监控键盘的线程。

    graph LR
    w32k.sys中2个线程鼠标监控线程键盘监控线程-->储存到对应的消息队列中

    那么鼠标监控线程怎么通过窗口,找到它的线程,然后找到消息队列呢?

    通过CreateWindow跟踪,发现在0环画的。
    窗口对象结构图有个成员,这个结构体在0环。

    _WINDOW_OBJECT的结构:
    
    ... 
    PTHREADINFO pti;    //所属线程
    ...
    

    所以在某个窗口点击,鼠标监控线程知道哪个窗口,根据窗口成员,找到线程,把这个点击事件放到这个线程的消息队列里。

    窗口句柄是全局的,hwnd不是地址,是0环表的索引,就是窗口句柄表。是公共的一张表,不是私有的。

    总结

    1. 窗口是在0环创建的

    2. 窗口句柄是全局的

    3. 一个线程可以用多个窗口,但每个窗口只能属于一个线程

    消息的接收

    一个GUI线程有一个消息队列

    graph LR
    普通线程-->GUI线程
    GUI线程-->THREAD.W32THREAD
    THREAD.W32THREAD-->THREADINFO
    THREADINFO-->消息队列

    一个线程可以有多个窗口,所有窗口共享一个消息队列

    graph LR
    _WINDOW_OBJECT-->PTHREADINFO_pti_//所属线程
    _WINDOW_OBJECT-->WNDPROC_lpfnWndProc_//窗口过程即窗口回调函数
    

    代码解析

    2

    消息队列的结构

    消息队列其实是一组,有7个

    <1> SentMessagesListHead    //接到SendMessage发来的消息
    
    <2> PostedMessagesListHead  //接到PostMessage发来的消息
    
    <3> HardwareMessagesListHead    //接到鼠标、键盘的消息
    
    ...

    GetMessage的功能

    GetMessage(     LPMSG lpMsg,        //返回从队列中摘下来的消息,把取出消息放在这
            HWND hWnd,      //过滤条件一:发个这个窗口的消息,取哪个窗口的消息
            UNIT wMsgFilterMin, //过滤条件
            UNIT wMsgFilterMax  //过滤条件
    );
    GetMessage先看SentMessagesListHead有没有消息,有的话立马处理。即使没有Trans和Dispatch也可以处理。   
    
    

    GetMessage的主要功能:

    循环判断是否有该窗口的消息,如果有,将消息存储到MSG指定的结构,并将消息从列表中删除。

    NtUserGetMessage的执行流程

    User32!GetMessage 调用 w32k!NtUserGetMessage
    do
    {
        //先判断SentMessagesListHead是否有消息 如果有处理掉
        do
        {
            ....
            KeUserModeCallback(USER32_CALLBACK_WINDOWPROC,
                                   Arguments,
                                   ArgumentLength,
                                   &ResultPointer,
                                   &ResultLength);
            ....
        }while(SentMessagesListHead != NULL)
        //以此判断其他的6个队列,里面如果有消息 返回  没有继续
    }while(其他队列!=NULL)
    

    SendMessage与PostMessage的区别(同步和异步)

    getMessage处理完才发送SendMessage接到值,接受到的通过共享内存返回结果发送sendmessage的程序才继续,所以是同步。
    postmessage被处理是在Transmessage

    消息的分发

    MSG msg;                        
    while(GetMessage(&msg, NULL, 0, 0))                 
    {       
        TranslateMessage(&msg); //这个是处理键盘按键的码,转换成字符,比如有这个处理,可以用消息码WM_CHAR得到是字符,没有的话就只能用WM_KEYDOWN,得到的是asiic码。
    
        DispatchMessage(&msg);  
    }  
    

    SentMessagesListHead由GetMessage()处理。

    其他队列的处理流程

    User32!DispatchMessage 调用 w32k!NtUserDispatchMessage

    <1> 根据窗口句柄找到窗口对象

    <2> 根据窗口对象得到窗口过程函数,由0环发起调用

    内核回调机制

    还有谁调用了窗口过程

    <1> GetMessage()在处理SentMessagesListHead中消息时,在0环通过KeUserCallback,实现了一个回调,调用了3换的函数。

    <2> DispatchMessage()在处理其他队列中的消息时

    <3> 内核代码,比如CreateWindow通过0环函数调用了KeUserCallback,KeUserCallback直接从0环调用3环的窗口回调。

    这三个都是从0环发起调用3环的函数。

    KeUserModeCallback的执行流程

    1、从0环调用3环函数的几种方式:
    APC、异常、内核回调

    2、凡是有窗口的程序就有可能0环直接调用3环的程序。回调机制中0环调用3环的的代码是函数:KeUserModeCallback

    3、回到3环的落脚点:
    APC:ntdll!KiUserApcDispatcher
    异常:ntdll!KiUserExceptionDispatcher

    4、内核回调在3环的落脚点:

    PEB
        +0x2C   回调函数地址表(由User32.dll提供)
    
    KeUserModeCallback函数的第一个参数就是索引
    

    参考资料

    [1] 滴水视频

    展开全文
  • Windows消息传递机制详解

    万次阅读 多人点赞 2015-03-19 13:44:34
    Windows是一个消息(Message)驱动系统。Windows消息提供了应用程序之间、应用程序与Windows系统之间进行通信的手段。应用程序想要实现的功能由消息来触发,并且靠对消息的响应和处理来完成。必须注意的是,消息...

     林炳文Evankaka原创作品。转载请注明出处http://blog.csdn.net/evankaka

         Windows是一个消息(Message)驱动系统。Windows的消息提供了应用程序之间、应用程序与Windows系统之间进行通信的手段。应用程序想要实现的功能由消息来触发,并且靠对消息的响应和处理来完成。必须注意的是,消息并非是抢占性的,无论事件的缓急,总是按照到达的先后派对,依次处理(一些系统消息除外),这样可能使一些实时外部事件得不到及时处理。

          Windows的应用程序一般包含窗口(Window),它主要为用户提供一种可视化的交互方式,窗口是总是在某个线程(Thread)内创建的。Windows系统通过消息机制来管理交互,消息(Message)被发送,保存,处理,一个线程会维护自己的一套消息队列(Message Queue),以保持线程间的独占性。队列的特点无非是先进先出,这种机制可以实现一种异步的需求响应过程。

    目录:

    1、消息

    2  、消息类型

    3 、消息队列(Message Queues)

    4 、队列消息(Queued Messages)和非队列消息(Non-Queued Messages)

    5 、PostMessage(PostThreadMessage), SendMessage

    6 、GetMessage, PeekMessage

    7 、TranslateMessage, TranslateAccelerator

    8、(消息死锁( Message Deadlocks)

    9、BroadcastSystemMessage

    10、消息的处理

    11、MFC的消息映射

    12、消息反射机制


    1、消息

        消息系统对于一个win32程序来说十分重要,它是一个程序运行的动力源泉。一个消息,是系统定义的一个32位的值,他唯一的定义了一个事件,向Windows发出一个通知,告诉应用程序某个事情发生了。例如,单击鼠标、改变窗口尺寸、按下键盘上的一个键

    都会使Windows发送一个消息给应用程序。

        消息本身是作为一个记录传递给应用程序的,这个记录中包含了消息的类型以及其他信息。例如,对于单击鼠标所产生的消息来

    说,这个记录中包含了单击鼠标时的坐标。这个记录类型叫做MSG,MSG含有来自windows应用程序消息队列的消息信息,它在

    Windows中声明如下: 
    typedef struct tagMsg 

    HWND hwnd;          // 接受该消息的窗口句柄 
    UINT message;         // 消息常量标识符,也就是我们通常所说的消息号 
    WPARAM wParam;     // 32位消息的特定附加信息,确切含义依赖于消息值 
    LPARAM lParam;       // 32位消息的特定附加信息,确切含义依赖于消息值 
    DWORD time;         // 消息创建时的时间 
    POINT pt;             // 消息创建时的鼠标/光标在屏幕坐标系中的位置 
    }MSG; 
        消息可以由系统或者应用程序产生。系统在发生输入事件时产生消息。举个例子, 当用户敲键, 移动鼠标或者单击控件。系统也
    产生消息以响应由应用程序带来的变化, 比如应用程序改变系统字体,改变窗体大小。应用程序可以产生消息使窗体执行任务,或者与其他应用程序中的窗口通讯。

    2、消息类型

    1) 系统定义消息(System-Defined Messages)
            在SDK中事先定义好的消息,非用户定义的,其范围在[0x0000, 0x03ff]之间, 可以分为以下三类: 
    1> 窗口消息(Windows Message)
           与窗口的内部运作有关,如创建窗口,绘制窗口,销毁窗口等。可以是一般的窗口,也可以是Dialog,控件等。 
    如:WM_CREATE, WM_PAINT, WM_MOUSEMOVE, WM_CTLCOLOR, WM_HSCROLL... 
    2> 命令消息(Command Message)
            与处理用户请求有关, 如单击菜单项或工具栏或控件时, 就会产生命令消息。 
    WM_COMMAND, LOWORD(wParam)表示菜单项,工具栏按钮或控件的ID。如果是控件, HIWORD(wParam)表示控件消息类型 
    3> 控件通知(Notify Message)
            控件通知消息, 这是最灵活的消息格式, 其Message, wParam, lParam分别为:WM_NOTIFY, 控件ID,指向NMHDR的指针。NMHDR包含控件通知的内容, 可以任意扩展。 
    2) 程序定义消息(Application-Defined Messages)
            用户自定义的消息, 对于其范围有如下规定: 
    WM_USER: 0x0400-0x7FFF      (ex. WM_USER+10) 
    WM_APP(winver> 4.0): 0x8000-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发送的消息是非队列消息, 被直接送到窗口过程处理.

    队列消息和非队列消息的区别
            从消息的发送途径来看,消息可以分成2种:队列消息和非队列消息。消息队列由可以分成系统消息队列和线程消息队列。系统消息队列由Windows维护,线程消息队列则由每个GUI线程自己进行维护,为避免给non-GUI现成创建消息队列,所有线程产生时并没有消息队列,仅当线程第一次调用GDI函数时系统才给线程创建一个消息队列。队列消息送到系统消息队列,然后到线程消息队列;非队列消息直接送给目的窗口过程。
         对于队列消息,最常见的是鼠标和键盘触发的消息,例如WM_MOUSERMOVE,WM_CHAR等消息,还有一些其它的消息,例如:WM_PAINT、 WM_TIMER和WM_QUIT。当鼠标、键盘事件被触发后,相应的鼠标或键盘驱动程序就会把这些事件转换成相应的消息,然后输送到系统消息队列,由 Windows系统去进行处理。Windows系统则在适当的时机,从系统消息队列中取出一个消息,根据前面我们所说的MSG消息结构确定消息是要被送往那个窗口,然后把取出的消息送往创建窗口的线程的相应队列,下面的事情就该由线程消息队列操心了,Windows开始忙自己的事情去了。线程看到自己的消息队列中有消息,就从队列中取出来,通过操作系统发送到合适的窗口过程去处理。
         一般来讲,系统总是将消息Post在消息队列的末尾。这样保证窗口以先进先出的顺序接受消息。然而,WM_PAINT是一个例外,同一个窗口的多个 WM_PAINT被合并成一个 WM_PAINT 消息, 合并所有的无效区域到一个无效区域。合并WM_PAIN的目的是为了减少刷新窗口的次数。

          非队列消息将会绕过系统队列和消息队列,直接将消息发送到窗口过程,。系统发送非队列消息通知窗口,系统发送消息通知窗口。例如,当用户激活一个窗口系统发送WM_ACTIVATE, WM_SETFOCUS, and WM_SETCURSOR。这些消息通知窗口它被激活了。非队列消息也可以由当应用程序调用系统函数产生。例如,当程序调用SetWindowPos系统发送WM_WINDOWPOSCHANGED消息。一些函数也发送非队列消息,例如下面我们要谈到的函数。

    5 、PostMessage(PostThreadMessage), SendMessage
            PostMessage:把消息放到指定窗口所在的线程消息队列中后立即返回。 PostThreadMessage:把消息放到指定线程的消息队列中后立即返回。 
            SendMessage:直接把消息送到窗口过程处理, 处理完了才返回。

    PostMessage(异步)和SendMessage(同步)的区别

    a、 PostMessage 是异步的,SendMessage 是同步的。

             PostMessage 只把消息放到队列,不管消息是不是被处理就返回,消息可能不被处理;

            SendMessage等待消息被处理完了才返回,如果消息不被处理,发送消息的线程将一直处于阻塞状态,等待消息的返回。

    b、 同一个线程内:

              SendMessage 发送消息时,由USER32.DLL模块调用目标窗口的消息处理程序,并将结果返回,SendMessage 在同一个线程里面发送消息不进入线程消息队列;PostMessage 发送的消息要先放到消息队列,然后通过消息循环分派到目标窗口(DispatchMessage)。

    c、不同线程:

                 SendMessage 发送消息到目标窗口的消息队列,然后发送消息的线程在USER32。DLL模块内监视和等待消息的处理结果,直到目标窗口的才处理返回,SendMessage在返回之前还需要做许多工作,如响应别的线程向它发送的SendMessage().PostMessge() 到别的线程的时候最好使用PostThreadMessage   代替。PostMessage()的HWND 参数可以为NULL,相当于PostThreadMessage() + GetCrrentThreadId.

    d、系统处理消息。

           系统只处理(marshal)系统消息(0--WM_USER),发送用户消息(用户自己定义)时需要用户自己处理。

            使用PostMessage,SendNotifyMessage,SendMessageCallback等异步函数发送系统消息时,参数不可以使用指针,因为发送者不等待消息的处理就返回,接收者还没有处理,指针就有可能被释放了,或则内容变化了。

    e、在Windows 2000/XP,每个消息队列最多只能存放一定数量的消息,超过的将不会被处理就丢掉。系统默认是10000;:[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows] USERPostMessageLimit

    6 、GetMessage, PeekMessage
    PeekMessage会立即返回    可以保留消息 
    GetMessage在有消息时返回  会删除消息

    PeekMessage和GetMessage函数的主要区别有:
            a. GetMessage的主要功能是从消息队列中“取出”消息,消息被取出以后,就从消息队列中将其删除;而PeekMessage的主要功能是“窥视”消息,如果有消息,就返回true,否则返回false。也可以使用PeekMessage从消息队列中取出消息,这要用到它的一个参数(UINT wRemoveMsg),如果设置为PM_REMOVE,消息则被取出并从消息队列中删除;如果设置为PM_NOREMOVE,消息就不会从消息队列中取出。
            b. 如果GetMessage从消息队列中取不到消息,则线程就会被操作系统挂起,等到OS重新调度该线程时,两者的性质不同:使用GetMessage线程仍会被挂起,使用PeekMessage线程会得到CPU的控制权,运行一段时间。
            c、GetMessage每次都会等待消息,直到取到消息才返回;而PeekMessage只是查询消息队列,没有消息就立即返回,从返回值判断是否取到了消息。
    我们也可以说,PeekMessage是一个具有线程异步行为的函数,不管消息队列中是否有消息,函数都会立即返回。而GetMessage则是一个具有线程同步行为的函数,如果消息队列中没有消息的话,函数就会一直等待,直到消息队列中至少有一条消息时才返回。
    如果消息队列中没有消息,PeekMessage总是能返回,这就相当于在执行一个循环,如果消息队列一直为空, 它就进入了一个死循环。GetMessage则不可能因为消息队列为空而进入死循环。

    联系:

            在Windows的内部,GetMessage和PeekMessage执行着相同的代码,Peekmessage和Getmessage都是向系统的消息队列中取得消息,并将其放置在指定的结构。

    区别:

    PeekMessage:有消息时返回TRUE,没有消息返回FALSE 

    GetMessage:有消息时且消息不为WM_QUIT时返回TRUE,如果有消息且为WM_QUIT则返回FALSE,没有消息时不返回。

    GetMessage:取得消息后,删除除WM_PAINT消息以外的消息。

    PeekMessage:取得消息后,根据wRemoveMsg参数判断是否删除消息。PM_REMOVE则删除,PM_NOREMOVE不删除。

    The PeekMessage function normally does not remove WM_PAINT messages from the queue. WM_PAINT messages remain in the queue until they are processed. However, if a WM_PAINT message has a null update region, PeekMessage does remove it from the queue.

    不能用PeekMessage从消息队列中删除WM_PAINT消息,从队列中删除WM_PAINT消息可以令窗口显示区域的失效区域变得有效(刷新窗口),如果队列中包含WM_PAINT消息程序就会一直while循环了。

    7 、TranslateMessage, TranslateAccelerator
             TranslateMessage: 把一个virtual-key消息转化成字符消息(character message),并放到当前线程的消息队列中,消息循环下一次取出处理。 
            TranslateAccelerator: 将快捷键对应到相应的菜单命令。它会把WM_KEYDOWN 或 WM_SYSKEYDOWN转化成快捷键表中相应的WM_COMMAND 或WM_SYSCOMMAND消息, 然后把转化后的 WM_COMMAND或WM_SYSCOMMAND直接发送到窗口过程处理, 处理完后才会返回。

    8、(消息死锁( Message Deadlocks)
    假设有线程A和B, 现在有以下下步骤 
            1) 线程A SendMessage给线程B, A等待消息在线程B中处理后返回 
            2) 线程B收到了线程A发来的消息,并进行处理, 在处理过程中,B也向线程A SendMessgae,然后等待从A返回。  因为此时, 线程A正等待从线程B返回, 无法处理B发来的消息, 从而导致了线程A,B相互等待, 形成死锁。多个线程也可以形成环形死锁。 
    可以使用 SendNotifyMessage或SendMessageTimeout来避免出现死锁。

    9、BroadcastSystemMessage
            我们一般所接触到的消息都是发送给窗口的, 其实, 消息的接收者可以是多种多样的,它可以是应用程序(applications), 可安装驱动(installable drivers), 网络设备(network drivers), 系统级设备驱动(system-level device drivers)等, 
    BroadcastSystemMessage这个API可以对以上系统组件发送消息。

    10、消息的处理
            接下来我们谈一下消息的处理,首先我们来看一下VC中的消息泵:

    while(GetMessage(&msg, NULL, 0, 0))
    {
           if(!TranslateAccelerator(msg.hWnd, hAccelTable, &msg))

                TranslateMessage(&msg);
                DispatchMessage(&msg);
           }
    }

    TranslateMessage(转换消息):

            用来把虚拟键消息转换为字符消息。由于Windows对所有键盘编码都是采用虚拟键的定义,这样当按键按下时,并不得字符消息,需要键盘映射转换为字符的消息。

    TranslateMessage函数

            用于将虚拟键消息转换为字符消息。字符消息被投递到调用线程的消息队列中,当下一次调用GetMessage函数时被取出。当我们敲击键盘上的某个字符键时,系统将产生WM_KEYDOWN和WM_KEYUP消息。这两个消息的附加参数(wParam和lParam)包含的是虚拟键代码和扫描码等信息,而我们在程序中往往需要得到某个字符的ASCII码,TranslateMessage这个函数就可以将WM_KEYDOWN和WM_ KEYUP消息的组合转换为一条WM_CHAR消息(该消息的wParam附加参数包含了字符的ASCII码),并将转换后的新消息投递到调用线程的消息队列中。注意,TranslateMessage函数并不会修改原有的消息,它只是产生新的消息并投递到消息队列中。

    也就是说TranslateMessage会发现消息里是否有字符键的消息,如果有字符键的消息,就会产生WM_CHAR消息,如果没有就会产生什么消息。

    DispatchMessage(分派消息):

    把 TranslateMessage转换的消息发送到窗口的消息处理函数,此函数在窗口注册时已经指定。

            首先,GetMessage从进程的主线程的消息队列中获取一个消息并将它复制到MSG结构,如果队列中没有消息,则GetMessage函数将等待一个消息的到来以后才返回。如果你将一个窗口句柄作为第二个参数传入GetMessage,那么只有指定窗口的的消息可以从队列中获得。GetMessage也可以从消息队列中过滤消息只接受消息队列中落在范围内的消息。这时候就要利用GetMessage/PeekMessage指定一个消息过滤器。这个过滤器是一个消息标识符的范围或者是一个窗体句柄,或者两者同时指定。当应用程序要查找一个后入消息队列的消息是很有用。WM_KEYFIRST 和 WM_KEYLAST 常量用于接受所有的键盘消息。 WM_MOUSEFIRST 和 WM_MOUSELAST 常量用于接受所有的鼠标消息。
    然后TranslateAccelerator判断该消息是不是一个按键消息并且是一个加速键消息,如果是,则该函数将把几个按键消息转换成一个加速键消息传递给窗口的回调函数。处理了加速键之后,函数TranslateMessage将把两个按键消息WM_KEYDOWN和WM_KEYUP转换成一个 WM_CHAR,不过需要注意的是,消息WM_KEYDOWN,WM_KEYUP仍然将传递给窗口的回调函数。    
    处理完之后,DispatchMessage函数将把此消息发送给该消息指定的窗口中已设定的回调函数。如果消息是WM_QUIT,则 GetMessage返回0,从而退出循环体。应用程序可以使用PostQuitMessage来结束自己的消息循环。通常在主窗口的 WM_DESTROY消息中调用。

    11、MFC的消息映射
             使用MFC编程时,消息发送和处理的本质和Win32相同,但是,它对消息处理进行了封装,简化了程序员编程时消息处理的复杂性,它通过消息映射机制来处理消息,程序员不必去设计和实现自己的窗口过程。
    说白了,MFC中的消息映射机制实质是一张巨大的消息及其处理函数对应表。消息映射基本上分为两大部分:
    在头文件(.h)中有一个宏DECLARE_MESSAGE_MAP(),它放在类的末尾,是一个public属性的;与之对应的是在实现部分(.cpp)增加了一个消息映射表,内容如下:
    BEGIN_MASSAGE_MAP(当前类,当前类的基类)
    //{{AFX_MSG_MAP(CMainFrame)
    消息的入口项
    //}}AFX_MSG_MAP
    END_MESSAGE_MAP()
    但是仅是这两项还不足以完成一条消息,要是一个消息工作,必须还有以下3个部分去协作:
    1、在类的定义中加入相应的函数声明;
    2、在类的消息映射表中加入相应的消息映射入口项;
    3、在类的实现中加入相应的函数体;
    消息的添加
    (1)、利用Class Wizard实现自动添加
            在菜单中选择View -&gt; Class Wizard激活Class Wizard,选择Message Map标签,从Class name组合框中选取我们想要添加消息的类。在Object IDs列表框中,选取类的名称。此时,Messages列表框显示该类的可重载成员函数和窗口消息。可重载成员函数显示在列表的上部,以实际虚构成员函数的大小写字母来表示。其他为窗口消息,以大写字母出现。选中我们要添加的消息,单击Add Funtion按钮,Class Wizard自动将该消息添加进来。
    有时候,我们想要添加的消息在Message列表中找不到,我们可以利用Class Wizard上Class Info标签以扩展消息列表。在该页中,找到Message Filter组合框,通过它可以改变首页中Messages列表框中的选项。
    (2)、手动添加消息
     如果Messages列表框中确实没有我们想要的消息,就需要我们手工添加:
            1)在类的.h文件中添加处理函数的声明,紧接着在//}}AFX_MSG行之后加入声明,注意,一定要以afx_msg开头。
    通常,添加处理函数声明的最好的地方是源代码中Class Wizard维护的表的下面,在它标记其领域的{{ }}括弧外面。这些括弧中的任何东西都有可能会被Class Wizard销毁。
            2)接着,在用户类的.cpp文件中找到//}}AFX_MSG_MAP行,紧接在它之后加入消息入口项。同样,也放在{{ }}外面。
            3)最后,在该文件中添加消息处理函数的实体。
    对于能够使用Class Wizard添加的消息,尽量使用Class Wizard添加,以减少我们的工作量;对于不能使用Class Wizard添加的消息和自定义消息,需要手动添加。总体说来,MFC的消息编程对用户来说,相对比较简单,在此不再使用实例演示。
            12、消息反射机制
    什么叫消息反射?
             父窗口将控件发给它的通知消息,反射回控件进行处理(即让控件处理这个消息),这种通知消息让控件自己处理的机制叫做消息反射机制。
            通过前面的学习我们知道,一般情况下,控件向父窗口发送通知消息,由父窗口处理这些通知消息。这样,父窗口(通常是一个对话框)会对这些消息进行处理,换句话说,控件的这些消息处理必须在父窗口类体内,每当我们添加子控件的时候,就要在父窗口类中复制这些代码。很明显,这对代码的维护和移植带来了不便,而且,明显背离C++的对象编程原则。
            从4.0版开始,MFC提供了一种消息反射机制(Message Reflection),可以把控件通知消息反射回控件。具体地讲,对于反射消息,如果控件有该消息的处理函数,那么就由控件自己处理该消息,如果控件不处理该消息,则框架会把该消息继续送给父窗口,这样父窗口继续处理该消息。可见,新的消息反射机制并不破坏原来的通知消息处理机制。
    消息反射机制为控件提供了处理通知消息的机会,这是很有用的。如果按传统的方法,由父窗口来处理这个消息,则加重了控件对象对父窗口的依赖程度,这显然违背了面向对象的原则。若由控件自己处理消息,则使得控件对象具有更大的独立性,大大方便了代码的维护和移植。
            实例M8:简单地演示MFC的消息反射机制。(见附带源码 工程M8)
         开VC++ 6.0,新建一个基于对话框的工程M8。
    在该工程中,新建一个CMyEdit类,基类是CEdit。接着,在该类中添加三个变量,如下:
    private:
    CBrush m_brBkgnd;
    COLORREF m_clrBkgnd;
    COLORREF m_clrText;
    在CMyEdit::CMyEdit()中,给这三个变量赋初值:
    {
    m_clrBkgnd = RGB( 255, 255, 0 );
    m_clrText = RGB( 0, 0, 0 );
    m_brBkgnd.CreateSolidBrush(RGB( 150, 150, 150) );
    }
    打开ClassWizard,类名为CMyEdit,Messages处选中“=WM_CTLCOLOR”,您是否发现,WM_CTLCOLOR消息前面有一个等号,它表示该消息是反射消息,也就是说,前面有等号的消息是可以反射的消息。
    消息反射函数代码如下:
    HBRUSH CMyEdit::CtlColor(CDC* pDC, UINT nCtlColor)
    {
        // TODO: Change any attributes of the DC here
        pDC-&gt;SetTextColor( m_clrText );//设置文本颜色
        pDC-&gt;SetBkColor( m_clrBkgnd );//设置背景颜色
         //请注意,在我们改写该函数的内容前,函数返回NULL,即return NULL;
        //函数返回NULL将会执行父窗口的CtlColor函数,而不执行控件的CtlColor函数
        //所以,我们让函数返回背景刷,而不返回NULL,目的就是为了实现消息反射
        return m_brBkgnd; //返回背景刷
    }
    在IDD_M8_DIALOG对话框中添加一个Edit控件,使用ClassWizard给该Edit控件添加一个CMyEdit类型的变量m_edit1,把Edit控件和CMyEdit关联起来。

    林炳文Evankaka原创作品。转载请注明出处http://blog.csdn.net/evankaka

    展开全文
  • 详谈Windows消息循环机制

    千次阅读 2017-11-16 19:10:15
    一直对windows消息循环不太清楚,今天做个详细的总结,如果说错的地方,请务必指出。 用VS2017新建一个win32 Application的默认代码如下: 程序入口 //int WINAPI WinMain 定义窗口类 //typedef struct ...

    一直对windows消息循环不太清楚,今天做个详细的总结,有说错的地方,请务必指出。

    用VS2017新建一个win32 Application的默认代码如下:

    程序入口                                          //intWINAPI WinMain
    定义窗口类                                      //typedef struct tagWNDCLASSA
    注册窗口类                                      //RegisterClass(&wndclass))
    生成窗口                                          //CreateWindow
    更新窗口                                          //ShowWindow(hwnd,iCmdShow);
    显示窗口                                          //UpdateWindow(hwnd);
    消息循环                                          //while(GetMessage(&msg,NULL,0,0))
    窗口消息处理(回调函数)                     //LRESULT CALLBACK WndProc

    消息循环的代码如下:
    while(GetMessage(&Msg, NULL, 0, 0) > 0) 
    { 
        TranslateMessage(&Msg);    //转换
        DispatchMessage(&Msg);     //分发
    } 



    这里有几个概念,容易混淆:
    1.系统:
    特指windows操作系统

    2.应用程序:
    指一个程序,比如QQ,或者酷狗之类的都算一个应用程序

    3.窗口:
    每个应用程序都 可以拥有窗口,而且可以有多个,但一般会有一个主窗口。例如QQ的主窗口,但是QQ也有很多类似于设置窗口的子窗口,这些窗口都属于QQ应用程序。

    4.消息:
    window系统定义了很多种消息,例如,单击鼠标、改变窗口尺寸、按下键盘,这些操作都会使Windows发送一个消息给应用程序。消息本身是作为一个记录传递给应用程序的,这个记录中包含了消息的类型以及其他信息

    5.消息循环:
    window系统的一种消息机制

    6.消息队列:
    是属于线程的,是windows系统为线程创建并维护的一个队列,用于存放各类消息。 系统自身维护一个系统消息队列,然后还为每个GUI线程线程维护一个线程专门消息队列。

    7.线程:
    每个线程默认是没有消息队列的,线程只有在第一次调用用户接口时(比如创建窗口,或者是操作UI元素时),系统才为其创建消息队列。一个应用程序可以有多个线程,但只能有一个UI线程,默认为主线程,其他子线程是无法操作UI并创建UI元素的。这是windows规定的


    windows消息循环的详细过程:

    1.我们创建完win32应用程序,当用户通过对鼠标,键盘操作应用程序时,由于Windows一直监控着I/O设备,该事件首先会被转化成消息,由windows系统捕获,存放于系统消息队列。

    2.Windows系统知道该消息应由哪个应用程序处理,然后拷贝到相应的应用程序消息队列。同时将该消息从系统消息队列中删除。

    3.应用程序的消息循环不断在执行,此时,调用GetMessage()从消息队列中查找消息,发现了该消息,GetMessage()将返回一个正值,并获取到了该消息Msg;PS:如果消息队列为空,程序将停止执行并等待(程序阻塞)。

    4. 然后取出消息(Msg)并将其传递给TranslateMessage()函数,这个函数做一些额外的处理:将虚拟键值信息转换为字符信息。这一步实际上是可选的,但有些地方需要用到这一步。

    5. 上面的步骤执行完后,将消息MSG传递给DispatchMessage()函数。DispatchMessage()函数将消息再给windows系统,由windows系统找到目标窗口并分发给该窗口,调用消息对应的窗口过程函数,既窗口的WinPro函数,让WinPro函数处理。WinPro函数可以允许我们对不同的消息做特定的处理,若不处理的话,则会调用DefWindowProc函数,做默认处理,所以为什么默认代码中WinPro的类型是CallBack(回调),因为不是我们主动调用的,是系统回调的

    6. 一旦一个消息处理完成,窗口过程WinPro函数返回,DispatchMessage()函数返回,应用程序的消息循环继续while循环,Window系统继续监控各类消息,重复上述步骤

    参考资料:http://blog.csdn.net/hellochenlian/article/details/41745799


    展开全文
  • Windows 常用消息大全

    万次阅读 2014-02-15 11:19:40
    表A-1 Windows消息分布 消息范围 说 明 0 ~ WM_USER – 1 系统消息 WM_USER ~ 0x7FFF 自定义窗口类整数消息 WM_APP ~ 0xBFFF 应用程序自定义消息 ...
  • 1.窗口Windows程序是由一系列的窗口构成的,每个窗口都自己的窗口过程,窗口过程就是一个拥有固定 Signature 的 C函数,具体格式如下:LRESULT CALLBACK WindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM ...
  • Windows消息队列

    千次阅读 2018-09-21 23:21:37
    7-20 Windows消息队列 (25 分) 消息队列是Windows系统的基础。对于每个进程,系统维护一个消息队列。如果在进程中特定事件发生,如点击鼠标、文字改变等,系统将把这个消息加到队列当中。同时,如果队列不是...
  • windows消息机制详解

    千次阅读 2015-04-09 00:10:47
    1. 引言 Windows 在操作系统平台占有绝对统治地位,基于Windows 的编程...而很多Windows 程序开发人员也只是对消息运行机制一知半解, 想要掌握Windows 编程的核心,必须深刻理解消息机制。事件驱动围绕着消息
  • windows自定义消息

    千次阅读 2014-07-10 10:29:16
    当我们通过这种方式注册应用程序自定义消息,
  • Windows 消息机制

    千次阅读 2013-05-20 20:22:20
    Windows消息机制要点 1. 窗口过程  每个窗口会一个称为窗口过程的回调函数(WndProc),它带四个参数,分别为:窗口句柄(Window Handle),消息ID(Message ID),和两个消息参数(wParam, lParam), 当窗口...
  • Windows消息循环机制

    千次阅读 2015-07-13 17:25:52
    理解消息循环和整个消息传送机制对Windows编程来说非常重要。如果对消息处理的整个过程不了解,在windows编程中会遇到很多令人困惑的地方。 什么是消息(Message) 每个消息是一个整型数值,如果查看头文件(查看...
  • windows消息机制概述(一)

    千次阅读 2017-07-08 17:52:02
    1. 引言 Windows 在操作系统平台占有绝对统治地位,基于...而很多Windows 程序开发人员也只是对消息运行机制一知半解,想要掌握Windows 编程的核心,必须深刻理解消息机制。事件驱动围绕着消息的产生与处理展开,
  • Windows消息队列 (c语言)

    千次阅读 2018-09-22 14:26:35
    消息队列是Windows系统的基础。对于每个进程,系统维护一个消息队列。如果在进程中特定事件发生,如点击鼠标、文字改变等,系统将把这个消息加到队列当中。同时,如果队列不是空的,这一进程循环地从队列中按照...
  • windows消息发送与接收

    千次阅读 2016-07-02 21:37:01
    能搞清楚消息的传递和处理,相信可以使我们对Windows程序更深的理解。 先把消息划分为3类:发送消息(Incomingsent message)、投递消息(Post message)、输入消息(Input message)。其中发送消息是非队列消息,...
  • Windows 中常见的消息类型及其说明

    千次阅读 2018-09-06 22:19:11
    Windows message 消息 消息类型 消息说明 参数说明 WM_CREATE 当创建一个窗口时响应 WM_DESTOROY 当摧毁一个窗口时响应 WM_MOVE 当窗口被移动时响应 WM_SIZE 当窗口...
  • Windows运行机理——消息消息队列

    千次阅读 2018-03-17 14:10:12
    Windows运行机理这系列文章都是来至于《零基础学Qt4编程》——吴迪,个人觉得写得很好,所以进行了搬运和个人加工Windows程序设计时一种基于消息的时机驱动方式的设计模式,完全不同于传动的DOS方式的程序设计方法,...
  • Windows应用程序是以消息为基础、以事件为驱动的应用程序。当应用程序在运行时,会不断地等待操作系统发送给自身的消息(while循环),然后根据不同的消息进行相应的处理。这里的消息实际上就是一种数据结构,包含窗口...
  • 深入Windows内核——C++中的消息机制

    万次阅读 多人点赞 2015-06-14 21:31:37
    《编程思想之消息机制》一文中我们讲了消息的相关概念和消息机制的模拟,本文将进一步聊聊C++中的消息机制。从简单例子探析核心原理在讲之前,我们先...Windows系统和Windows下的程序都是以消息为基础,以事件为驱动。
  • 在win32控制台程序中,如何获取windows消息?比如键盘和鼠标事件的消息。
  • windows消息机制总结

    千次阅读 2018-07-25 16:57:38
    DOS靠过程来驱动,而windows消息驱动。 MFC/QT是一个广泛使用的变成类库,对windows消息机制进行了很好的封装。 1、消息的概念: 消息是指windows操作系统发给应用程序的一个通告,告诉程序某个特定的事件...
  • WINDOWS消息处理过程

    千次阅读 2015-04-07 10:42:35
    WINDOWS消息处理过程   一、引言 二、Windows消息机制的概念 1、DOS与Windows驱动机制的区别 2、消息 3、消息的来源 4、Windows的消息系统的组成 5、消息的响应 三、Windows消息机制要点 1. 窗口过程 2 ...
  • Qt 之进程间通信(Windows 消息

    万次阅读 热门讨论 2016-01-26 19:29:48
    简述通过上一节的了解,我们可以看出进程通信的方式很多,今天分享下如何利用Windows消息机制来进行不同进程间的通信。简述 效果 发送消息 自定义类型与接收窗体 发送数据 接收消息 设置标题 重写nativeEvent 更多...
  • Java窗口如何接收并处理windows消息

    千次阅读 2016-09-05 14:22:01
    当前的java窗口若需要接收到window的消息,首先需要获取到当前窗口的句柄,然后通过窗口句柄得到其window消息回调函数,最后截获消息回调函数中有用的消息,并将无用的消息还给window消息处理函数进行处理。...
  • Windows官方纯净系统镜像下载及相关介绍

    万次阅读 多人点赞 2019-06-11 23:24:21
    1、Windows 10 (Multiple Editions) (x64) - DVD (Chinese-Simplified),这个是64位系统的Windows 10 ,为多个版本的混合,如家庭版,专业版,在安装时,可供我们做选择,一般我们选择专业版即可。 Windows 10 (Multiple ...
  • Windows编程捕获特定窗口及键盘鼠标消息模拟一、简介二、使用到的API简介三、获取Windows记事本的编辑区窗口的句柄并模拟输入操作1、分析2、代码一3、代码二四、后记 一、简介 窗口是Windows的核心组件,Windoows下...
  • C# 发送、接收和处理自定义的WINDOWS消息  为了程序启动后自动执行主函数,在Form1_Load中直接执行启动函数,可能造成没有反应。当然,在Form1_Load中加入较长时间(比如2秒)的定时器,在定时器函数中关闭...
  • Windows程序是由一系列的窗口构成的,每个窗口都自己的窗口过程,窗口过程就是一个拥有固定 Signature 的 C函数,具体格式如下: LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM...
  • Windows消息机制

    万次阅读 2018-03-14 00:18:36
    一、消息队列 首先我们要明确一个观点:窗口是和线程相关联的,消息队列也是和线程相关联的,这个线程无论是主线程还是子线程。 当一个线程被创建时,系统假定该线程不会被用于任何与用户界面相关的任务,所以不会...
  • 深入理解windows 消息机制

    万次阅读 多人点赞 2013-11-07 15:52:12
    深入理解Windows消息机制    今天我们来学一学Windows消息机制,我们知道在传统的C语音程序中,当我们需要打开一个文件时,我们可以调用fopen()函数,这个函数最后又会调用操作系统提供的函数以此来打开文件。而...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 440,643
精华内容 176,257
关键字:

windows消息有哪些