【轉】深入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++中的消息機制》

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM