• 【转】深入Windows内核——C++中的消息机制


    上节讲了消息的相关概念,本文将进一步聊聊C++中的消息机制。

    从简单例子探析核心原理

    在讲之前,我们先看一个简单例子:创建一个窗口和两个按钮,用来控制窗口的背景颜色。其效果

    图1.效果图

                                                                                                         

     Win32Test.h

     1 #pragma once
     2 
     3 #include <windows.h>
     4 #include <atltypes.h>
     5 #include <tchar.h>
     6 
     7 //资源ID
     8 #define ID_BUTTON_DRAW      1000
     9 #define ID_BUTTON_SWEEP     1001
    10 
    11 // 注册窗口类
    12 ATOM AppRegisterClass(HINSTANCE hInstance);
    13 // 初始化窗口
    14 BOOL InitInstance(HINSTANCE, int);
    15 // 消息处理函数(又叫窗口过程)
    16 LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
    17 // (白色背景)按钮事件
    18 void OnButtonWhite();
    19 // (灰色背景)按钮事件
    20 void OnButtonGray();
    21 // 绘制事件
    22 void OnDraw(HDC hdc);

    Win32Test.cpp

      1 #include "stdafx.h"
      2 #include "Win32Test.h"
      3 
      4 
      5 //字符数组长度
      6 #define MAX_LOADSTRING 100
      7 
      8 //全局变量
      9 HINSTANCE hInst;                                            // 当前实例
     10 TCHAR g_szTitle[MAX_LOADSTRING] = TEXT("Message process");  // 窗口标题
     11 TCHAR g_szWindowClass[MAX_LOADSTRING] = TEXT("AppTest");    // 窗口类的名称
     12 HWND g_hWnd;                                                // 窗口句柄
     13 bool g_bWhite = false;                                      // 是否为白色背景
     14 
     15 //WinMain入口函数
     16 int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
     17 {
     18     UNREFERENCED_PARAMETER(hPrevInstance);
     19     UNREFERENCED_PARAMETER(lpCmdLine);
     20     // 注册窗口类
     21     if(!AppRegisterClass(hInstance))
     22     {
     23          return (FALSE);
     24     }
     25     // 初始化应用程序窗口
     26     if (!InitInstance (hInstance, nCmdShow))
     27     {
     28         return FALSE;
     29     }
     30 
     31     // 消息循环
     32     MSG msg;
     33     while (GetMessage(&msg, NULL, 0, 0))
     34     {
     35         TranslateMessage(&msg);
     36         DispatchMessage(&msg);
     37     }
     38     return (int) msg.wParam;
     39 }
     40 
     41 
     42 
     43 // 注册窗口类
     44 ATOM AppRegisterClass(HINSTANCE hInstance)
     45 {
     46     WNDCLASSEX wcex;
     47     wcex.cbSize = sizeof(WNDCLASSEX);
     48     wcex.style          = CS_HREDRAW | CS_VREDRAW;
     49     wcex.lpfnWndProc    = WndProc;
     50     wcex.cbClsExtra     = 0;
     51     wcex.cbWndExtra     = 0;
     52     wcex.hInstance      = hInstance;
     53     wcex.hIcon          = LoadIcon(NULL, IDI_APPLICATION);
     54     wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
     55     wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
     56     wcex.lpszMenuName   = NULL;
     57     wcex.lpszClassName  = g_szWindowClass;
     58     wcex.hIconSm        = NULL;
     59 
     60     return RegisterClassEx(&wcex);
     61 }
     62 
     63 
     64 
     65 // 保存实例化句柄并创建主窗口
     66 BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
     67 {  
     68    hInst = hInstance; // 保存handle到全局变量
     69    g_hWnd = CreateWindow(g_szWindowClass, g_szTitle, WS_OVERLAPPEDWINDOW, 0, 0, 400, 300, NULL, NULL, hInstance, NULL);
     70    // 创建按钮
     71    HWND hBtWhite = CreateWindowEx(0, L"Button", L"白色", WS_CHILD | WS_VISIBLE | BS_TEXT, 100, 100, 50, 20, g_hWnd, (HMENU)ID_BUTTON_DRAW, hInst, NULL);
     72    HWND hBtGray = CreateWindowEx(0, L"Button", L"灰色", WS_CHILD | WS_VISIBLE | BS_CENTER, 250, 100, 50, 20, g_hWnd, (HMENU)ID_BUTTON_SWEEP, hInst, NULL);
     73 
     74    if (!g_hWnd)
     75    {
     76       return FALSE;
     77    }
     78    ShowWindow(g_hWnd, nCmdShow);
     79    UpdateWindow(g_hWnd);
     80 
     81    return TRUE;
     82 }
     83 
     84 
     85 
     86 // (窗口)消息处理
     87 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
     88 {
     89     int wmId, wmEvent;
     90     PAINTSTRUCT ps;
     91     HDC hdc;
     92 
     93     switch (message)
     94     {
     95     case WM_COMMAND:
     96         wmId    = LOWORD(wParam);
     97         //wmEvent = HIWORD(wParam);
     98 
     99         switch (wmId)
    100         {
    101         case ID_BUTTON_DRAW:
    102             OnButtonWhite();
    103             break;
    104         case ID_BUTTON_SWEEP:
    105             OnButtonGray();
    106             break;
    107         default:
    108             return DefWindowProc(hWnd, message, wParam, lParam);
    109         }
    110         break;
    111     case WM_PAINT:
    112         hdc = BeginPaint(hWnd, &ps);
    113         OnDraw(hdc);
    114         EndPaint(hWnd, &ps);
    115         break;
    116     case WM_DESTROY:
    117         PostQuitMessage(0);
    118         break;
    119     default:
    120         return DefWindowProc(hWnd, message, wParam, lParam);
    121     }
    122     return 0;
    123 }
    124 
    125 
    126 
    127 //事件处理
    128 
    129 //按下hBtWhite时的事件
    130 void OnButtonWhite()
    131 {
    132     g_bWhite = true;
    133     InvalidateRect(g_hWnd, NULL, FALSE);    //刷新窗口
    134 }
    135 
    136 //按下hBtGray时的事件
    137 void OnButtonGray()
    138 {
    139     g_bWhite = false;
    140     InvalidateRect(g_hWnd, NULL, FALSE);    //刷新窗口
    141 }
    142 
    143 //绘制事件(每次刷新时重新绘制图像)
    144 void OnDraw(HDC hdc)
    145 {   
    146     POINT oldPoint;
    147     SetViewportOrgEx(hdc, 0, 0, &oldPoint);
    148     RECT rcView;
    149     GetWindowRect(g_hWnd, &rcView); // 获得句柄的画布大小
    150     HBRUSH hbrWhite = (HBRUSH)GetStockObject(WHITE_BRUSH);
    151     HBRUSH hbrGray = (HBRUSH)GetStockObject(GRAY_BRUSH);
    152     if (g_bWhite)
    153     {
    154         FillRect(hdc, &rcView, hbrWhite);
    155     } else
    156     {
    157         FillRect(hdc, &rcView, hbrGray);
    158     }
    159     SetViewportOrgEx(hdc, oldPoint.x, oldPoint.y, NULL);
    160 }

    在上面这个例子中,消息的流经过程如下:

    图2.消息的流经过程

     

    这与《编程思想之消息机制》中图1(消息机制原理)是相吻合的,这就是Windows消息机制的核心部分,也是Windows API开发的核心部分。Windows系统和Windows下的程序都是以消息为基础,以事件为驱动

    RegisterClassEx的作用是注册一个窗口,在调用CreateWindow创建一个窗口前必须向windows系统注册获惟一的标识。

    1 while (GetMessage(&msg, NULL, 0, 0))
    2 {
    3     TranslateMessage(&msg);
    4     DispatchMessage(&msg);
    5 }

     

    这个while循环就是消息循环,不断地从消息队列中获取消息,并通过DispatchMessage(&msg)将消息分发出去。消息队列是在Windows操作系统中定义的(我们无法看到对应定义的代码),对于每一个正在执行的Windows应用程序,系统为其建立一个“消息队列”,即应用程序队列,用来存放该程序可能创建的各种窗口的消息。DispatchMessage会将消息传给窗口函数(即消息处理函数)去处理,也就是WndProc函数。WndProc是一个回调函数,在注册窗口时通过wcex.lpfnWndProc将其传给了操作系统,所以DispatchMessage分发消息后,操作系统会调用窗口函数(WndProc)去处理消息。

    每一个窗口都应该有一个函数负责消息处理,程序员必须负责设计这个所谓的窗口函数Wndproc。

    LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

    中的四个参数就是消息的相关信息(消息来自的句柄、消息类型等),函数中通过switch/case根据不同的消息类型分别进行不同的处理。在收到相应类型的消息之后,可调用相应的函数去处理,如OnButtonWhite、OnButtonGray、OnDraw,这就是事件处理的雏形。 在default中调用了DefWindowProc,DefWindowProc是操作系统定义的默认消息处理函数,这是因为所有的消息都必须被处理,应用程序不处理的消息需要交给操作系统处理

    消息的定义和类型

    Windows消息都以WM_为前缀,意思是“Windows Message” ,如WM_CREATE、WM_PAINT等。消息的定义如下:

    1 typedef struct tagMsg
    2 {   
    3     HWND    hwnd;           //接受该消息的窗口句柄
    4     UINT    message;        //消息常量标识符,也就是我们通常所说的消息号
    5     WPARAM  wParam;         //32位消息的特定附加信息,确切含义依赖于消息值
    6     LPARAM  lParam;         //32位消息的特定附加信息,确切含义依赖于消息值
    7     DWORD   time;           //消息创建时的时间
    8     POINT   pt;             //消息创建时的鼠标/光标在屏幕坐标系中的位置
    9 }MSG;

    消息主要有三种类型:

    1. 命令消息(WM_COMMAND):命令消息是程序员需要程序做某些操作的命令。凡UI对象产生的消息都是这种命令消息,可能来自菜单、加速键或工具栏按钮等,都以WM_COMMAND呈现。 
    2. 标准窗口消息:除WM_COMMAND之处,任何以WM_开头的消息都是这一类。标准窗口消息是系统中最为常见的消息,它是指由操作系统和控制其他窗口的窗口所使用的消息。例如CreateWindow、DestroyWindow和MoveWindow等都会激发窗口消息,以及鼠标移动、点击,键盘输入都是属于这种消息。 
    3. Notification:这种消息由控件产生,为的是向其父窗口(通常是对话框窗口)通知某种情况。当一个窗口内的子控件发生了一些事情,而这些是需要通知父窗口的,此刻它就上场啦。通知消息只适用于标准的窗口控件如按钮、列表框、组合框、编辑框,以及Windows公共控件如树状视图、列表视图等。

     

     队列消息 和非队列消息

    Windows中有一个系统消息队列,对于每一个正在执行的Windows应用程序,系统为其建立一个“消息队列”,即应用程序队列,用来存放该程序可能创建的各种窗口的消息。 

    (1)队列消息(Queued Messages) 
    消息会先保存在消息队列中,通过消息循环从消息队列中获取消息并分发到各窗口函数去处理,如鼠标、键盘消息就属于这类消息。 
    (2)非队列消息(NonQueued Messages) 
    就是消息会直接发送到窗口函数处理,而不经过消息队列。 如: WM_ACTIVATE, WM_SETFOCUS, WM_SETCURSOR, WM_WINDOWPOSCHANGED就属于此类。

    PostMessage与SendMessage的区别

    PostMessage发送的消息是队列消息,它会把消息Post到消息队列中; SendMessage发送的消息是非队列消息, 被直接送到窗口过程处理,等消息被处理后才返回。

    图3.消息队列示意图

     

    为证明这一过程,我们可以改动一下上面的这个例子。

    1.在Win32Test.h中添加ID_BUTTON_TEST的定义

    1 #define ID_BUTTON_TEST      1002

    2.在OnButtonWhite中分别用SendMessage和PostMessage发送消息

    1 //按下hBtWhite时的事件
    2 void OnButtonWhite()
    3 {
    4     g_bWhite = true;
    5     InvalidateRect(g_hWnd, NULL, FALSE);    //刷新窗口
    6     SendMessage(g_hWnd, WM_COMMAND, ID_BUTTON_TEST, 0);
    7     //PostMessage(g_hWnd, WM_COMMAND, ID_BUTTON_TEST, 0);
    8 }

    3.在消息循环中增加ID_BUTTON_TEST的判断

    1 while (GetMessage(&msg, NULL, 0, 0))
    2     {
    3         if (LOWORD(msg.wParam) == ID_BUTTON_TEST)
    4         {
    5             OutputDebugString(L"This is a ID_BUTTON_TEST message.");    // [BreakPoint1]
    6         }
    7         TranslateMessage(&msg);
    8         DispatchMessage(&msg);
    9     }

    4.在窗口处理函数WndProc增加ID_BUTTON_TEST的判断

     1 case ID_BUTTON_TEST:
     2     {
     3         OutputDebugString(L"This is a ID_BUTTON_TEST message.");        // [BreakPoint2]
     4     }
     5     break;
     6 case ID_BUTTON_DRAW:
     7     OnButtonWhite();
     8     break;
     9 case ID_BUTTON_SWEEP:
    10     OnButtonGray();
    11     break;

    用断点调试的方式我们发现,用SendMessage发送的ID_BUTTON_TEST消息只会进入BreakPoint2,而PostMessage发送的ID_BUTTON_TEST会进入到BreakPoint1和BreakPoint2。

    转自 luoweifu 《深入Windows内核——C++中的消息机制》

  • 相关阅读:
    django之认证权限和接口
    序列化组件
    django中cbv源码和restful规范
    AjaxControlToolKit--TabContainer控件的介绍收藏[摘录]
    sublime text 3 激活
    sublime 2激活和解决中文乱码
    sublime text2 保存文件时候名字后缀.dump问题解决
    选项卡 刷新回原来页面的处理方法:
    关于C#自定义控件【摘录】
    比较好的GridView固定问题(链接)
  • 原文地址:https://www.cnblogs.com/codingmengmeng/p/6117226.html
Copyright © 2020-2023  润新知