程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 深入Windows內核——C++中的消息機制

深入Windows內核——C++中的消息機制

編輯:C++入門知識

深入Windows內核——C++中的消息機制


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


從簡單例子探析核心原理

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

白色窗口 灰色窗口
圖 2 :效果圖

 

Win32Test.h

#pragma once

#include 
#include 
#include 

//資源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, LButton, L白色, WS_CHILD | WS_VISIBLE | BS_TEXT, 100, 100, 50, 20, g_hWnd, (HMENU)ID_BUTTON_DRAW, hInst, NULL);
   HWND hBtGray = CreateWindowEx(0, LButton, 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 :消息隊列示意圖

 <喎?http://www.Bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPs6q1qTD99Xi0ru5/bPMo6zO0sPHv8nS1LjEtq/Su8/Cyc/D5rXE1eK49sD919OhozxiciAvPg0KMS7U2ldpbjMyVGVzdC5o1tDM7bzTSURfQlVUVE9OX1RFU1S1xLao0uU8L3A+DQo8cHJlIGNsYXNzPQ=="brush:java;"> #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(LThis is a ID_BUTTON_TEST message.);    // [BreakPoint1]
        }
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

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

case ID_BUTTON_TEST:
    {
        OutputDebugString(LThis 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。

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved