从内核层说清GetMessage , DispatchMessage

要点回顾:

一个GUI线程有一个消息队列:
普通线程–>GUI线程–>THREAD.W32THREAD -->THREADINFO–>消息队列

一个线程可以有多个窗口,所有窗口共享一个消息队列:
_WINDOW_OBJECT ---->PTHREADINFO pti //所属线程
---->WNDPROC IpfnWndProc //窗口过程(窗口回调函数)

为什么拿到句柄非得要回零环?

GetMessage(&msg, NULL, 0, 0)
TranslateMessage(&msg);
DispatchMessage(&msg);

这里消息msg的结构体成员如下:

typedef struct tagMSG {
    
    
  HWND   hwnd;
  UINT   message;
  WPARAM wParam;
  LPARAM lParam;
  DWORD  time;
  POINT  pt;
  DWORD  lPrivate;
} MSG, *PMSG, *NPMSG, *LPMSG;

这个消息里面存放着窗口的句柄,句柄是什么?句柄它仅仅是一个窗口对象的索引而已,并非当前对象地址,通过句柄找不到相应的窗口回调,它仅仅就是一个窗口对象索引值。窗口回调是存储在窗口对象里面的,如果需要找到窗口回调,那么我们就需要先找到窗口对象,而窗口对象在哪呢?(窗口与线程的关系)窗口都是由API进入零环去画出来,一切信息都在零环,这个知识点我们在前面已经说过。。

所以GetMessageTranslateMessageDispatchMessage拿着取出来的消息的句柄,进入零环,通过句柄找到相应的窗口对象,通过窗口对象找到相对于的窗口回调函数,然后内核进行调用回调函数。

这也就是为什么非得进入零环的原因。

消息队列(总共有7个小队列)结构

1.SentMessagesListHead //接到SendMessage发来的消息
2.PostedMessagesListHead //接到PostMessage发来的消息
3.HardwareMessagesListHead //接到鼠标,键盘的消息
…………
…………
根据前面的介绍,消息队列放在THREADINFOTHREADINFOKTHREAD结构体中,KTHREAD又在ETHREAD中)中:
在这里插入图片描述
USER_MESSAGE_QUEUE又分为七个小队列
在这里插入图片描述
在这里插入图片描述

GetMessage的声明:

GetMessage(LPMSG	IpMsg,			//返回从队列中摘下来的消息
    HWND	  hWnd,				//过滤条件一:(要取的是哪个窗口的消息,如果要专门取哪个窗口的消息,直接把句柄放在此处就行)发个这个窗口的消息
    UINT	wMsgFilterMin,		//过滤条件
    UINT    wMsgFilterMax		//过滤条件;

GetMessage进入内核:

GetMessage会调用内核层函数w32k!NtUserGetMessage,伪代码如下:

do{
    
    
//先判断SentMessageListHead
do{
    
    
……
KeUserModeCallBack(USER32_CALLBACK_WINDOWPROC,
					Arguments,
					ArgumentLength,
					&ResultPointer,
					&ResultLength);
…………
}while(SentMessageListHead!=NULL}while(其他队列!=NULL

在这里插入图片描述

扫描二维码关注公众号,回复: 12581177 查看本文章

进入后可以查看到这里有处理SentMessagesListHead 消息队列的函数:
在这里插入图片描述
然后进入后这是从0环回到3环的函数:
在这里插入图片描述
GetMessage只处理SendMessage发来的消息,原因可以看上图,而由PostMessage发来的消息,只是取出,并不会进行近一步处理操作

GetMessage的功能总结:

GetMessage(只处理第一个消息队列的消息,至于其它消息队列的消息,GetMessage只负责取出, 然后不管,继续向下传递)的主要功能:

  1. (第一个循环)首先会判断SentMessagesListHead 这个队列里面有没有消息,如果有的话,首先会把这个消息给处理掉(如何处理呢?也就是从0环回到3环,再来调用注册的窗口过程函数)
  2. (第二个循环:依次判断其他的6个队列,里面如果有消息,就返回,没有就继续取消息)循环判断是否有该窗口的消息,如果有,将消息存储到MSG指定的结构,并将消息从列表中删除(依次判断其他的6个队列,里面如果有消息 返回,没有 继续)
    它会首先看SentMessagesListHead 这个队列,如果有的话,会就地处理

DispatchMessage

DispatchMessage(&msg)//消息的分发,根据窗口句柄调用相关的窗口过程,通过不同的句柄,进入零环找到不同的窗口对象,然后根据窗口对象找到回调函数,并且调用回调函数。

即其他6个消息队列的处理流程:
User32!DispatchMessage调用w32k!NtUserDispatchMessage

  1. 根据窗口句柄找到窗口对象
  2. 根据窗口对象找到窗口过程处理函数,由0环发起调用
    在这里插入图片描述

举例验证(有前提情况,仔细观察)

TranslateMessage(&msg);DispatchMessage(&msg);注释掉后,只剩GetMessage(&msg, NULL, 0, 0),然后利用其它程序PostMessage(hwnd, 0x0401, NULL, NULL);SendMessage(hwnd, 0x0401, NULL, NULL);分别发送消息
前提情况:(特别注意)
在这里插入图片描述

SendMessage发送消息运行截图

SendMessage(hwnd, 0x0401, NULL, NULL);

在这里插入图片描述
下图中我们可以看到
在这里插入图片描述
当我们未点击确定时,发送消息的程序未退出,需要点击确定后,发送消息的程序收到返回消息,它才会自行退出。这也就是SendMessage的同步问题

当点击确定后,发送消息程序的运行截图:
在这里插入图片描述

PostMessage发送消息运行截图

PostMessage(hwnd, 0x0401, NULL, NULL);

发送消息的运行截图:
在这里插入图片描述
接收消息的运行截图:
在这里插入图片描述
这里充分说明了上述情况,GetMessage并不会处理PostMessage发送的消息。

注意:
PostMessage发送完消息后,程序即刻退出,并不会等待处理结果,这也就是PostMessage发送消息异步问题

猜你喜欢

转载自blog.csdn.net/CSNN2019/article/details/113832191