要点回顾
前篇学习了关于消息的接收,主要是围绕GetMessage函数展开的,了解到GetMessage函数不仅仅读取消息,还会处理SentMessagesListHead队列中的消息。并且消息队列不止一个,共有七个。本篇学习了解消息的分发,主要是围绕DispatchMessage展开。
核心逻辑
关于处理窗口消息的核心逻辑如下,这里再回顾下:
1 | MSG msg; |
其它队列的消息处理
消息队列共有7个队列,现在已经知道GetMessage可以用来处理SentMessagesListHead中的消息,也就是SendMessage函数发来的消息,那么仍然有6个队列的消息未被处理。
DispatchMessage
(实验:注释掉DispatchMessage函数后运行窗口程序并给窗口发送消息,这里省略代码及实验过程,以后补上)
在注释掉DispatchMessage函数的情况下,对于窗口的任何操作(鼠标点击或者敲击键盘)始终是没有反应的,显然没有了DispatchMessage函数,除SendMessage发送的消息外均无法被处理。
现在可以确认DispatchMessage是用来处理其余队列中消息的函数,但想要了解本质,就得进一步跟进这个函数。
(这里省略跟进过程,比较简单,所以不想跟了)
不用想,这个DispatchMessage肯定是没有做处理的,因为这个函数根本没有进入0环,而窗口对应的结构体_WINDOW_OBJECT位于0环,不进入0环,怎么调用窗口过程函数的呢?所以DispatchMessage只是一个入口,用来调用win32k.sys中提供的函数NtUserDispatchMessage。这个函数完成了对消息的分发,它做了下面2件事:
- 根据窗口句柄找到窗口对象_WINDOW_OBJECT。
- 根据窗口对象得到窗口过程函数,由0环发起调用。
这样也产生了两个问题,第一,窗口句柄哪里来?第二,如何从0环发起调用?关于从0环发起调用,与GetMessage一样,都是调用一个叫做KeUserModeCallback的回调函数进入3环,再去调用窗口过程函数。至于窗口句柄,DispatchMessage只有一个参数msg,所以就要把目光放在msg上了。
Msg结构体
DispatchMessage只有一个参数msg,其结构如下:
1 | //msg |
可以看到这个msg里面,第一个成员就是窗口句柄,表明当前消息是发给哪个窗口的,前面说过,窗口句柄与内核对象句柄类似,都是提供给3环用的,DispatchMessage在调用了NtUserDispatchMessage后,就可以通过窗口句柄在窗口句柄表中找到对应_WINDOW_OBJECT结构体的地址,从而调用窗口回调函数,完成消息的分发。
消息的转换
DispatchMessage是用来消息的分发的,那么TranslateMessage有啥用呢?顾名思义,TranslateMessage起到翻译的作用,相当于一种优化。
(实验:分别构造WM_KEYDOWN与WM_CHAR两个回调函数,测试注释掉TranslateMessage函数的情况,此处省略,以后补上)
经过测试就可以发现,TranslateMessage是针对键盘类消息的一种优化,如果没有TranslateMessage,那么键盘消息属于WM_KEYDOWN类型,打印出来为ASCII对应的10进制的值,而使用TranslateMessage后,键盘消息会被转换成WM_CHAR类型,打印出来的就是键盘上按下的符号。所以有没有TranslateMessage影响不大,只是对消息的类型进行转换。
默认的消息处理函数
在窗口与线程一篇中就看到过,每时每刻的消息是非常多的,而又不能给每一种消息写一个窗口过程函数,那样太程序就臃肿了,所以一般来说,我们只对关注的消息设计窗口过程函数,其它消息可以交予Windows来管理。Windows也提供了默认的窗口过程函数,放在Default语句中即可:
1 | //默认窗口过程处理函数 |
总结
- DispatchMessage用于消息的分发,具体实现由底层的NtUserDispatchMessage完成,参数msg提供窗口句柄。
- TranslateMessage用于对键盘消息的转换。
- 大部分消息可以交由默认窗口过程处理函数DefWindowProc进行处理。
参考资料
参考教程:
- 海哥逆向中级预习班
参考链接:
- https://blog.csdn.net/weixin_42052102/article/details/83787929 (My classmates-消息机制学习笔记)