Windows消息机制

消息队列在哪

消息队列

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

消息队列放在哪里

Windows消息机制_第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_//窗口过程即窗口回调函数

代码解析

Windows消息机制_第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,内核,Windows编程)