码迷,mamicode.com
首页 > 其他好文 > 详细

消息机制

时间:2020-04-06 15:42:41      阅读:79      评论:0      收藏:0      [点我收藏+]

标签:lap   date   mamicode   turn   call   不能   鼠标键盘   mes   orm   

Windows内核分析索引目录:https://www.cnblogs.com/onetrainee/p/11675224.html

消息机制

1.最基本的窗口创建

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);        //声明用来处理消息的函数

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
    static TCHAR szAppName[] = TEXT("MyWindow");
    HWND hwnd;
    MSG msg;
    WNDCLASS wndclass;        //声明一个窗口类对象

    //以下为窗口类对象wndclass的属性
    wndclass.style = CS_HREDRAW | CS_VREDRAW;                         //窗口样式
    wndclass.lpszClassName = szAppName;                               //窗口类名
    wndclass.lpszMenuName = NULL;                                     //窗口菜单:无
    wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);    //窗口背景颜色
    wndclass.lpfnWndProc = WndProc;                                   //窗口处理函数
    wndclass.cbWndExtra = 0;                                          //窗口实例扩展:无
    wndclass.cbClsExtra = 0;                                          //窗口类扩展:无
    wndclass.hInstance = hInstance;                                   //窗口实例句柄
    wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);               //窗口最小化图标:使用缺省图标
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);                 //窗口采用箭头光标

    if (!RegisterClass(&wndclass))
    {    //注册窗口类, 如果注册失败弹出错误提示
        MessageBox(NULL, TEXT("窗口注册失败!"), TEXT("错误"), MB_OK | MB_ICONERROR);
        return 0;
    }

    hwnd = CreateWindow(                   //创建窗口
        szAppName,                 //窗口类名
        TEXT("我的窗口"),           //窗口标题
        WS_OVERLAPPEDWINDOW,       //窗口的风格
        CW_USEDEFAULT,             //窗口初始显示位置x:使用缺省值
        CW_USEDEFAULT,             //窗口初始显示位置y:使用缺省值
        CW_USEDEFAULT,             //窗口的宽度:使用缺省值
        CW_USEDEFAULT,             //窗口的高度:使用缺省值
        NULL,                      //父窗口:无
        NULL,                      //子菜单:无
        hInstance,                 //该窗口应用程序的实例句柄 
        NULL                       //
    );

    ShowWindow(hwnd, iCmdShow);        //显示窗口
    UpdateWindow(hwnd);                //更新窗口

    while (GetMessage(&msg, NULL, 0, 0))        //从消息队列中获取消息
    {
        TranslateMessage(&msg);                 //将虚拟键消息转换为字符消息
        DispatchMessage(&msg);                  //分发到回调函数(过程函数)
    }
    return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;                //设备环境句柄
    PAINTSTRUCT ps;         //绘制结构
    RECT rect;               //矩形结构

    switch (message)        //处理得到的消息
    {
    case WM_CREATE:          //窗口创建完成时发来的消息
        MessageBox(hwnd, TEXT("窗口已创建完成!"), TEXT("我的窗口"), MB_OK | MB_ICONINFORMATION);
        return 0;

    case WM_PAINT:           //处理窗口区域无效时发来的消息
        hdc = BeginPaint(hwnd, &ps);
        GetClientRect(hwnd, &rect);
        DrawText(hdc, TEXT("Hello, 这是我自己的窗口!"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
        EndPaint(hwnd, &ps);
        return 0;

    case WM_LBUTTONDOWN:     //处理鼠标左键被按下的消息
        MessageBox(hwnd, TEXT("鼠标左键被按下。"), TEXT("单击"), MB_OK | MB_ICONINFORMATION);
        return 0;

    case WM_DESTROY:         //处理窗口关闭时的消息
        MessageBox(hwnd, TEXT("关闭程序!"), TEXT("结束"), MB_OK | MB_ICONINFORMATION);
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);        //DefWindowProc处理我们自定义的消息处理函数没有处理到的消息
}

 

2.消息队列在哪里

  微软将消息队列放入零环,在_Kthread+0x130 Win32Thread中,里面指向GUI线程。

  1) 当线程刚创建的时候,都是普通线程

    Thread.serviceTable->KeServiceDescriptorTable。

  2)当线程第一次调用win32k.sys时,会调用一个函数:PsConvertToGuiThread

    其主要做以下几件事情:

    ① 扩充内核栈,将原来内核栈12KB换成64KB大小。

    ② 创建一个包含消息队列的结构体,并挂到KTHREAD上。

    ③ Thread.ServiceTable->KeServiceDescriptorTableShadow。

    ④ 把需要的内存数据映射到本进程空间中。

  因此,对于消息队列,在 _Kthread.Win32Thread+0xxx,里面存在一个USER_MESSAGE_QUEUE。

 

3.窗口与线程的关

  1)消息从哪里来,消息到哪儿去?

    win32k.sys创建两个线程,一个监控鼠标线程,一个监控键盘线程。

    桌面存在有关焦点,其很容易获取当前是存在哪个窗口,将其存储到窗口对应的线程的消息队列中。

  2)窗口对象 WINDOW_OBJECT

    每一个窗口控件,其本质是一个窗口,其对应在内核存在一个窗口对象。

    注意:并不是外面一个大窗口才是窗口,其按钮也是一个窗口,都对应一个父窗口。

    GUI线程与窗口对象的关系如下,消息队列存储在线程对象中。

    窗口句柄表是全局的,在三环都可以查询得到该表,因此我们在三环通过窗口回调函数来遍历全部窗口。

  3)窗口过程与窗口对象之间的关系

    窗口过程与窗口对象并不是一一对应的,窗口过程是消息的处理函数。

    不同的窗口对象可以使用一个处理函数,注册窗口时会声明该函数,不同窗口可以共用一个窗口回调,也可以使用自己单独的回调。

   技术图片

 

4.消息队列的接收

  1)消息结构

    其并不是一组消息队列,而是七组消息队列,其消息结构如下:

    SendMessageListHead接收SendMessage发过来的消息,Post···接收PostMessage发过来的消息。

    其中鼠标和键盘在<3>组中,键盘发过来的消息。

    技术图片

   2)GetMessage函数分析

    GetMessage(LPMSG lpMsg,HWND hWnd,UINT wMsgFilterMin,UINT wMsgFilterMax)

    该函数远不止获取过滤消息这么点事情,其还应该做很多事情

    ① 对SendMessageListHead中的消息进行处理

      其内核调用win32k.NtUserGetMessage函数,其处理过程如下:

      其会判断SendMessageListHead的消息,然后调用KeUserModeCallBack返回三环执行窗口回调函数。

      而其他的消息我们必须手动通过DispatchMessage来派发消息,但SendMessage发过来的在内核中就进行处理。

      do{

        ······

        do{

          KeUserModeCallBack(USER32_CALLBACK_WINDORPROC,xxx); // 返回三转直接回调

        }while(SendMessageListHead!=NULL);

        ······

      }while(其他队列!=NULL);

    ② SendMessage 与 PostMessage区别

      之前,我们在看API有关文档时,总会看到SendMessage同步,PostMessage异步。

      如果你理解上面所讲,则很好理解,SendMessage投递过去,立刻在内核中就会处理,因此SendMessage发过去,就会等待直到处理完成。

      但是PostMessage,发过去就立刻返回,因为无法确保立刻进行处理,这是非常重要的一部分。

 

5. 消息的分发

  如上面消息队列中所描述的,硬件(鼠标键盘)发过来的消息被存储到Hardxxx的消息队列中,其不是SendMessageListHead中,因此无法自行取出。

  所以,在GetMessage中获取消息,之后通过DispatchMessage来手动分发消息。

  1)为何GetMessage获取后无法直接调用函数而是要调用DispatchMessage。

    因为你在三环无法确定其到底是哪个窗口过程,只能得到窗口句柄,必须调用DispatchMessage回到零环。

    故DispatchMessage中的作用就是获取窗口句柄,回到内核找到对应的WINDOW_OBJECT,得到对应的窗口过程,来进行处理。

  2)TranslateMessage函数

    该函数的目的是将键盘的消息来进行转换,加工键盘消息,比如将键盘的65转为‘A‘。

 

6. 内核回调机制

  1)谁还调用了窗口过程:

    三类:① GetMessage处理SendMessage的;② DispatchMessage分发窗口过程; ③ 内核回调。

    我们之前已经介绍过①②,其还存在一个内核回调③,下面我们就来介绍这个。

  2)内核回调:

    在创建窗口过程中,CreateWindow内核创建时,其并不会通过①②途径来调用内核回调函数,而是直接在零环回调三环函数。

    KeUserModeCallBack函数 : 其所有的界面内核函数(win32k.sys)回三环都通过该函数来进行分析。

    因为其回到三环落脚点很多,所有不能通过APC和异常的方式来,其通过一张表来获取。

  3)内核回调表 PEB+0x2c:

    KeUserModeCallBack从零环返回三环时,落脚点不固定,因为全部界面内核函数返回三环都使用这张表。

    这张表位于_PEB+KernelCallBackTable +0x2c 处,我们通过OD就可以查看该表。

    如果我们想Hook这个函数,就直接来修改这张表即可。

    

消息机制

标签:lap   date   mamicode   turn   call   不能   鼠标键盘   mes   orm   

原文地址:https://www.cnblogs.com/onetrainee/p/12638823.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!