zoukankan      html  css  js  c++  java
  • 消息机制

    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这个函数,就直接来修改这张表即可。

        

  • 相关阅读:
    win10下安装Oracle 11g 32位客户端遇到INS-13001环境不满足最低要求
    kubernetes环境下私有仓库搭建
    Build a Restful Service
    大数据的开始:安装hadoop
    调试Ajax调用的利器firebug
    Oracle Process Cloud流程云实践
    Kubernetes环境下如何运行Coherence缓存集群
    Kubernetes的负载均衡问题(Nginx Ingress)
    Kubernetes master节点的高可用配置
    Kubernetes环境下调整WebLogic JVM基本参数
  • 原文地址:https://www.cnblogs.com/onetrainee/p/12638823.html
Copyright © 2011-2022 走看看