文章目录
要点回顾:
一个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进入零环去画出来,一切信息都在零环,这个知识点我们在前面已经说过。。
所以GetMessage
,TranslateMessage
,DispatchMessage
拿着取出来的消息的句柄,进入零环,通过句柄找到相应的窗口对象,通过窗口对象找到相对于的窗口回调函数,然后内核进行调用回调函数。
这也就是为什么非得进入零环的原因。
消息队列(总共有7个小队列)结构
1.SentMessagesListHead
//接到SendMessage
发来的消息
2.PostedMessagesListHead
//接到PostMessage
发来的消息
3.HardwareMessagesListHead
//接到鼠标,键盘的消息
…………
…………
根据前面的介绍,消息队列放在THREADINFO
(THREADINFO
在KTHREAD
结构体中,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)
进入后可以查看到这里有处理SentMessagesListHead
消息队列的函数:
然后进入后这是从0环回到3环的函数:
GetMessage
只处理SendMessage
发来的消息,原因可以看上图,而由PostMessage
发来的消息,只是取出,并不会进行近一步处理操作
GetMessage的功能总结:
GetMessage(只处理第一个消息队列的消息,至于其它消息队列的消息,GetMessage只负责取出, 然后不管,继续向下传递)的主要功能:
- (第一个循环)首先会判断
SentMessagesListHead
这个队列里面有没有消息,如果有的话,首先会把这个消息给处理掉(如何处理呢?也就是从0环回到3环,再来调用注册的窗口过程函数) - (第二个循环:依次判断其他的6个队列,里面如果有消息,就返回,没有就继续取消息)循环判断是否有该窗口的消息,如果有,将消息存储到MSG指定的结构,并将消息从列表中删除(依次判断其他的6个队列,里面如果有消息 返回,没有 继续)
它会首先看SentMessagesListHead
这个队列,如果有的话,会就地处理
DispatchMessage
DispatchMessage
(&msg
)//消息的分发,根据窗口句柄调用相关的窗口过程,通过不同的句柄,进入零环找到不同的窗口对象,然后根据窗口对象找到回调函数,并且调用回调函数。
即其他6个消息队列的处理流程:
User32!DispatchMessage
调用w32k!NtUserDispatchMessage
- 根据窗口句柄找到窗口对象
- 根据窗口对象找到窗口过程处理函数,由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发送消息异步问题