深入Windows核心——C++中的訊息機制

來源:互聯網
上載者:User

標籤:編程思想   訊息機制   c++   

《編程思想之訊息機制》一文中我們講了訊息的相關概念和訊息機制的類比,本文將進一步聊聊C++中的訊息機制。

從簡單例子探析核心原理

在講之前,我們先看一個簡單例子:建立一個視窗和兩個按鈕,用來控制視窗的背景顏色。其效果如下:


圖 2 :

Win32Test.h

#pragma once#include <windows.h>#include <atltypes.h>#include <tchar.h>//資源ID#define ID_BUTTON_DRAW      1000#define ID_BUTTON_SWEEP     1001// 註冊視窗類別ATOM AppRegisterClass(HINSTANCE hInstance);// 初始化視窗BOOL InitInstance(HINSTANCE, int);// 訊息處理函數(又叫視窗過程)LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);// (白色背景)按鈕事件void OnButtonWhite();// (灰色背景)按鈕事件void OnButtonGray();// 繪製事件void OnDraw(HDC hdc);

Win32Test.cpp

#include "stdafx.h"#include "Win32Test.h"//字元數組長度#define MAX_LOADSTRING 100//全域變數HINSTANCE hInst;                                            // 當前執行個體TCHAR g_szTitle[MAX_LOADSTRING] = TEXT("Message process");  // 視窗標題TCHAR g_szWindowClass[MAX_LOADSTRING] = TEXT("AppTest");    // 視窗類別的名稱HWND g_hWnd;                                                // 視窗控制代碼bool g_bWhite = false;                                      // 是否為白色背景//WinMain入口函數int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow){    UNREFERENCED_PARAMETER(hPrevInstance);    UNREFERENCED_PARAMETER(lpCmdLine);    // 註冊視窗類別    if(!AppRegisterClass(hInstance))    {         return (FALSE);    }    // 初始化應用程式視窗    if (!InitInstance (hInstance, nCmdShow))    {        return FALSE;    }    // 訊息迴圈    MSG msg;    while (GetMessage(&msg, NULL, 0, 0))    {        TranslateMessage(&msg);        DispatchMessage(&msg);    }    return (int) msg.wParam;}// 註冊視窗類別ATOM AppRegisterClass(HINSTANCE hInstance){    WNDCLASSEX wcex;    wcex.cbSize = sizeof(WNDCLASSEX);    wcex.style          = CS_HREDRAW | CS_VREDRAW;    wcex.lpfnWndProc    = WndProc;    wcex.cbClsExtra     = 0;    wcex.cbWndExtra     = 0;    wcex.hInstance      = hInstance;    wcex.hIcon          = LoadIcon(NULL, IDI_APPLICATION);    wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);    wcex.lpszMenuName   = NULL;    wcex.lpszClassName  = g_szWindowClass;    wcex.hIconSm        = NULL;    return RegisterClassEx(&wcex);}// 儲存執行個體化控制代碼並建立主視窗BOOL InitInstance(HINSTANCE hInstance, int nCmdShow){     hInst = hInstance; // 儲存handle到全域變數   g_hWnd = CreateWindow(g_szWindowClass, g_szTitle, WS_OVERLAPPEDWINDOW, 0, 0, 400, 300, NULL, NULL, hInstance, NULL);   // 建立按鈕   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);   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);   if (!g_hWnd)   {      return FALSE;   }   ShowWindow(g_hWnd, nCmdShow);   UpdateWindow(g_hWnd);   return TRUE;}// (視窗)訊息處理LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){    int wmId, wmEvent;    PAINTSTRUCT ps;    HDC hdc;    switch (message)    {    case WM_COMMAND:        wmId    = LOWORD(wParam);        //wmEvent = HIWORD(wParam);        switch (wmId)        {        case ID_BUTTON_DRAW:            OnButtonWhite();            break;        case ID_BUTTON_SWEEP:            OnButtonGray();            break;        default:            return DefWindowProc(hWnd, message, wParam, lParam);        }        break;    case WM_PAINT:        hdc = BeginPaint(hWnd, &ps);        OnDraw(hdc);        EndPaint(hWnd, &ps);        break;    case WM_DESTROY:        PostQuitMessage(0);        break;    default:        return DefWindowProc(hWnd, message, wParam, lParam);    }    return 0;}//事件處理//按下hBtWhite時的事件void OnButtonWhite(){    g_bWhite = true;    InvalidateRect(g_hWnd, NULL, FALSE);    //重新整理視窗}//按下hBtGray時的事件void OnButtonGray(){    g_bWhite = false;    InvalidateRect(g_hWnd, NULL, FALSE);    //重新整理視窗}//繪製事件(每次重新整理時重新繪製映像)void OnDraw(HDC hdc){       POINT oldPoint;    SetViewportOrgEx(hdc, 0, 0, &oldPoint);    RECT rcView;    GetWindowRect(g_hWnd, &rcView); // 獲得控制代碼的畫布大小    HBRUSH hbrWhite = (HBRUSH)GetStockObject(WHITE_BRUSH);    HBRUSH hbrGray = (HBRUSH)GetStockObject(GRAY_BRUSH);    if (g_bWhite)    {        FillRect(hdc, &rcView, hbrWhite);    } else    {        FillRect(hdc, &rcView, hbrGray);    }    SetViewportOrgEx(hdc, oldPoint.x, oldPoint.y, NULL);}

在上面這個例子中,訊息的流經過程如下:


圖 3 :訊息的流經過程
這與《編程思想之訊息機制》中圖1(訊息機制原理)是相吻合的,這就是Windows訊息機制的核心部分,也是Windows API開發的核心部分。 Windows系統和Windows下的程式都是以訊息為基礎,以事件為驅動。
RegisterClassEx的作用是註冊一個視窗,在調用CreateWindow建立一個視窗前必須向windows系統註冊獲惟一的標識。

while (GetMessage(&msg, NULL, 0, 0)){    TranslateMessage(&msg);    DispatchMessage(&msg);}

這個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等。訊息的定義如下:

typedef struct tagMsg{       HWND    hwnd;           //接受該訊息的視窗控制代碼    UINT    message;        //訊息常量標識符,也就是我們通常所說的訊息編號    WPARAM  wParam;         //32位訊息的特定附加資訊,確切含義依賴於訊息值    LPARAM  lParam;         //32位訊息的特定附加資訊,確切含義依賴於訊息值    DWORD   time;           //訊息建立時的時間    POINT   pt;             //訊息建立時的滑鼠/游標在螢幕座標系中的位置}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發送的訊息是非隊列訊息, 被直接送到視窗過程處理,等訊息被處理後才返回。


圖 4 :訊息佇列

為證明這一過程,我們可以改動一下上面的這個例子。
1.在Win32Test.h中添加ID_BUTTON_TEST的定義

#define ID_BUTTON_TEST      1002

2.在OnButtonWhite中分別用SendMessage和PostMessage發送訊息
//按下hBtWhite時的事件

void OnButtonWhite(){    g_bWhite = true;    InvalidateRect(g_hWnd, NULL, FALSE);    //重新整理視窗    SendMessage(g_hWnd, WM_COMMAND, ID_BUTTON_TEST, 0);    //PostMessage(g_hWnd, WM_COMMAND, ID_BUTTON_TEST, 0);}

3.在訊息迴圈中增加ID_BUTTON_TEST的判斷

while (GetMessage(&msg, NULL, 0, 0))    {        if (LOWORD(msg.wParam) == ID_BUTTON_TEST)        {            OutputDebugString(L"This is a ID_BUTTON_TEST message.");    // [BreakPoint1]        }        TranslateMessage(&msg);        DispatchMessage(&msg);    }

4.在視窗處理函數WndProc增加ID_BUTTON_TEST的判斷

case ID_BUTTON_TEST:    {        OutputDebugString(L"This is a ID_BUTTON_TEST message.");        // [BreakPoint2]    }    break;case ID_BUTTON_DRAW:    OnButtonWhite();    break;case ID_BUTTON_SWEEP:    OnButtonGray();    break;

用斷點調試的方式我們發現,用SendMessage發送的ID_BUTTON_TEST訊息只會進入BreakPoint2,而PostMessage發送的ID_BUTTON_TEST會進入到BreakPoint1和BreakPoint2。


如果您有什麼疑惑和想法,請在評論處給予反饋,您的反饋就是最好的測評師!由於本人技術和能力有限,如果本博文有錯誤或不足之處,敬請諒解並給出您寶貴的建議!


========================編程思想系列文章回顧========================
編程思想之訊息機制
編程思想之日誌記錄
編程思想之異常處理
編程思想之Regex
編程思想之迭代器
編程思想之遞迴
編程思想之回調

深入Windows核心——C++中的訊息機制

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.