Windows消息机制学习笔记(三)—— 消息的接收与分发
Windows消息機制學習筆記(三)—— 消息的接收與分發
- 要點回顧
- 消息循環
- 消息隊列
- 消息的接收
- GetMessage
- 實驗1:理解GetMessage
- 第一步:編譯并運行程序A
- 第二步:編譯并運行程序B
- 同步與異步
- 實驗2:理解同步與異步
- 第一步:編譯并運行實驗1中的程序A
- 第二步:編譯并運行實驗1中的程序B
- 第三步:點擊確定按鈕
- 第四步:修改程序B的代碼,再次運行
- 消息的分發
- DispatchMessage
- 實驗3:理解DispatchMessage
- 第一步:編譯并運行以下代碼
- 第二步:操作窗口
- 第三步:去掉DispatchMessage注釋,重新運行
- 第四步:再次操作窗口
- TranslateMessage
- 實驗4:理解TranslateMessage
- 第一步:編譯并運行以下代碼
- 第二步:敲擊不同按鍵
- 第三步:修改代碼并重新運行程序
- 第四步:敲擊不同按鍵
- 總結
要點回顧
1)一個GUI線程包含一個消息隊列
普通線程 ↓ GUI線程 ↓ THEWAD.W32THREAD ↓ THREADINFO ↓ 消息隊列2)一個線程可以包含多個窗口,所有窗口共享一個消息隊列
_WINDOW_OBJECT //0環創建 ↓ PTHREADINFO pti //所屬線程 ↓ WNDPROC lpfnWndProc //窗口過程(窗口回調函數)消息循環
MSG msg; while(GetMessage(&msg, NULL, 0, 0)) //從消息隊列中取出消息 {TranslateMessage(&msg); //加工消息DispatchMessage(&msg); //分發消息 }思考:使用SendMessage與PostMessage發送的消息位于同一組隊列中嗎?
答案:不同類型的消息被置于不同隊列。
消息隊列
描述:消息隊列共有七組,用于存放不同類型的消息。
例如:
完整隊列:
//ReactOS v3.12 typedef struct _USER_MESSAGE_QUEUE {.../* Owner of the message queue */struct _ETHREAD *Thread;/* Queue of messages sent to the queue. */LIST_ENTRY SentMessagesListHead;/* Queue of messages posted to the queue. */LIST_ENTRY PostedMessagesListHead;/* Queue of sent-message notifies for the queue. */LIST_ENTRY NotifyMessagesListHead;/* Queue for hardware messages for the queue. */LIST_ENTRY HardwareMessagesListHead;.../* messages that are currently dispatched by other threads */LIST_ENTRY DispatchingMessagesHead;/* messages that are currently dispatched by this message queue, required for cleanup */LIST_ENTRY LocalDispatchingMessagesHead;... }消息的接收
GetMessage
描述:從消息隊列中取出消息
BOOL?WINAPI?GetMessage(LPMSG lpMsg, //返回從隊列中取出的消息HWND hWnd, //過濾條件一:窗口句柄UINT wMsgFilterMin, //過濾條件二:最小值UINT wMsgFilterMax //過濾條件三:最大值 );主要功能:循環判斷是否存在屬于該窗口的消息,若存在,則將消息存儲到MSG指定的結構中,并將消息從列表中刪除。
注意:事實上,GetMessage還做了一件很重要的事情,即在接收消息時,將SentMessagesListHead中的消息進行處理
大致流程:
User32!GetMessage ↓ w32k!NtUsrGetMessage ↓ do {//先判斷SentMessagesListHead中是否存在消息,若存在則進行處理do{...KeUserModeCallback(USER32_CALLBACK_WINDOWPROC,Arguments,ArgumentLength,&ResultPinter,&ResultLength);...} }while(SentMessagesListHead != NULL)源碼(ReactOS v3.12):
BOOL APIENTRY NtUserGetMessage( PNTUSERGETMESSAGEINFO UnsafeInfo,HWND hWnd,UINT MsgFilterMin,UINT MsgFilterMax ) {...do{GotMessage = co_IntPeekMessage(&Msg, Window, MsgFilterMin, MsgFilterMax, PM_REMOVE);...}while (! GotMessage); //對應外層循環... } BOOL FASTCALL co_IntPeekMessage( PUSER_MESSAGE Msg,PWINDOW_OBJECT Window,UINT MsgFilterMin,UINT MsgFilterMax,UINT RemoveMsg ) {.../* Dispatch sent messages here. */while (co_MsqDispatchOneSentMessage(ThreadQueue)); //對應內層循環... } BOOLEAN FASTCALL co_MsqDispatchOneSentMessage(PUSER_MESSAGE_QUEUE MessageQueue) {...if (Message->HookMessage == MSQ_ISHOOK){...}else if (Message->HookMessage == MSQ_ISEVENT){...}else{/* 發送消息 */Result = co_IntSendMessage(Message->Msg.hwnd,Message->Msg.message,Message->Msg.wParam,Message->Msg.lParam);}/* 從消息隊列中刪除該消息 */RemoveEntryList(&Message->ListEntry);.../* 調用回調函數 */if (Message->CompletionCallback != NULL){co_IntCallSentMessageCallback(Message->CompletionCallback,Message->Msg.hwnd,Message->Msg.message,Message->CompletionCallbackContext,Result);}... } VOID APIENTRY co_IntCallSentMessageCallback(SENDASYNCPROC CompletionCallback,HWND hWnd,UINT Msg,ULONG_PTR CompletionCallbackContext,LRESULT Result) {.../* 回到三環進行處理 */Status = KeUserModeCallback(USER32_CALLBACK_SENDASYNCPROC,&Arguments,sizeof(SENDASYNCPROC_CALLBACK_ARGUMENTS),&ResultPointer,&ResultLength);... }實驗1:理解GetMessage
第一步:編譯并運行程序A
#include <windows.h>LRESULT CALLBACK WindowProc(IN HWND hwnd,IN UINT uMsg,IN WPARAM wParam,IN LPARAM lParam ){switch(uMsg){case 0x401:MessageBoxA(NULL, "測試窗口接收到消息", "新消息", MB_OK);return false;}return DefWindowProc(hwnd, uMsg, wParam, lParam); }int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd ){//窗口的類名TCHAR className[] = "My First Window";//創建一個自己的窗口WNDCLASS wndclass = {0};wndclass.hbrBackground = (HBRUSH)COLOR_MENU;wndclass.lpfnWndProc = WindowProc;wndclass.lpszClassName = className;wndclass.hInstance = hInstance;//注冊RegisterClass(&wndclass);//創建窗口HWND hwnd = CreateWindow(className,TEXT("測試窗口"),WS_OVERLAPPEDWINDOW,10,10,600,300,NULL,NULL,hInstance,NULL);if(hwnd == NULL)return 0;//顯示窗口ShowWindow(hwnd, SW_SHOW);//消息循環MSG msg;while(GetMessage(&msg, NULL, 0, 0)){//TranslateMessage(&msg);//DispatchMessage(&msg);}return 0; }運行結果:
注意:不要關閉窗口
第二步:編譯并運行程序B
#include <stdio.h> #include <windows.h>int main() {HWND hwnd = FindWindow("My First Window", "測試窗口");SendMessage(hwnd, 0x401, 0, 0);return 0; }運行結果:
同步與異步
描述:
實驗2:理解同步與異步
第一步:編譯并運行實驗1中的程序A
運行結果:
第二步:編譯并運行實驗1中的程序B
在return處設置斷點:
運行結果:
此時,程序B并未繼續向下執行。
第三步:點擊確定按鈕
執行結果:
程序A處理消息后,程序B繼續向下執行。
第四步:修改程序B的代碼,再次運行
新代碼:
#include <stdio.h> #include <windows.h>int main() {HWND hwnd = FindWindow("My First Window", "測試窗口");PostMessage(hwnd, 0x401, 0, 0); //SendMessage改為PostMessagereturn 0; }執行結果:
程序B發送消息后未等待目標處理繼續向下執行,程序A并未處理消息。
消息的分發
描述:GetMessage能夠處理SentMessagesListHead隊列中的消息,其他隊列中的消息則由DispatchMessage進行分發處理。
大致流程:
源碼(ReactOS v3.12):
LRESULT APIENTRY NtUserDispatchMessage(PMSG UnsafeMsgInfo) {...if (!Hit) Res = IntDispatchMessage(&SafeMsg);... } LRESULT FASTCALL IntDispatchMessage(PMSG pMsg) {...retval = co_IntCallWindowProc( Window->Wnd->lpfnWndProc,!Window->Wnd->Unicode,pMsg->hwnd,pMsg->message,pMsg->wParam,lParamPacked,lParamBufferSize);... } LRESULT APIENTRY co_IntCallWindowProc(WNDPROC Proc,BOOLEAN IsAnsiProc,HWND Wnd,UINT Message,WPARAM wParam,LPARAM lParam,INT lParamBufferSize) {.../* 回到三環,調用窗口過程函數 */Status = KeUserModeCallback(USER32_CALLBACK_WINDOWPROC,Arguments,ArgumentLength,&ResultPointer,&ResultLength);... }DispatchMessage
描述:用于消息分發,根據窗口句柄調用相關的窗口過程,通常用于分發由GetMessage函數檢索到的消息。
LRESULT DispatchMessage(CONST MSG *lpmsg // message information );思考:為什么DispatchMessage要對消息進行分發而不統一處理?
答案:所有窗口共享一個消息隊列,且每個窗口都擁有屬于自己的回調函數。
MSG結構體成員:
typedef struct tagMSG {HWND hwnd; //窗口句柄UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; } MSG, *PMSG;DispatchMessage根據MSG->hwnd調用各句柄對應窗口對象的回調函數。
思考:既然GetMessage已經得到了句柄信息,為什么不能直接調用對應的回調函數而需要分發。
答案:因為句柄指向的是句柄表,而不是窗口對象,需要進入0環通過句柄表找到窗口對象,才能調用對應的回調函數。
實驗3:理解DispatchMessage
第一步:編譯并運行以下代碼
#include <stdio.h> #include <windows.h>LRESULT CALLBACK WindowProc(IN HWND hwnd,IN UINT uMsg,IN WPARAM wParam,IN LPARAM lParam ){switch(uMsg){case 0x401:{MessageBoxA(NULL, "測試窗口接收到消息", "新消息", MB_OK);return 0;}case WM_DESTROY:{ExitProcess(0);return 0;}}return DefWindowProc(hwnd, uMsg, wParam, lParam); }int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd ){//窗口的類名TCHAR className[] = "My First Window";//創建一個自己的窗口WNDCLASS wndclass = {0};wndclass.hbrBackground = (HBRUSH)COLOR_MENU;wndclass.lpfnWndProc = WindowProc;wndclass.lpszClassName = className;wndclass.hInstance = hInstance;//注冊RegisterClass(&wndclass);//創建窗口HWND hwnd = CreateWindow(className,TEXT("測試窗口"),WS_OVERLAPPEDWINDOW,10,10,600,300,NULL,NULL,hInstance,NULL);if(hwnd == NULL)return 0;//顯示窗口ShowWindow(hwnd, SW_SHOW);//消息循環MSG msg;while(GetMessage(&msg, NULL, 0, 0)){//TranslateMessage(&msg);//DispatchMessage(&msg);}return 0; }運行結果:
第二步:操作窗口
拖動窗口、點擊最小化、縮放、關閉按鈕均無反應。
第三步:去掉DispatchMessage注釋,重新運行
MSG msg; while(GetMessage(&msg, NULL, 0, 0)) {//TranslateMessage(&msg);DispatchMessage(&msg); }第四步:再次操作窗口
拖動窗口、點擊最小化、縮放、關閉按鈕均得到相應。
TranslateMessage
描述:用于將虛擬鍵碼轉換為字符消息。該字符消息又被發送給對應線程(調用TranslateMessage的線程)的消息隊列,當線程再次調用GetMessage函數或PeekMessage函數獲取消息的時候被讀取。
實驗4:理解TranslateMessage
第一步:編譯并運行以下代碼
#include <stdio.h> #include <windows.h>LRESULT CALLBACK WindowProc(IN HWND hwnd,IN UINT uMsg,IN WPARAM wParam,IN LPARAM lParam ){TCHAR szBuffer[MAX_PATH];switch(uMsg){/*case WM_KEYDOWN:{sprintf(szBuffer, "鍵%d被你按下了\x00", wParam);MessageBox(NULL, szBuffer, "WM_KEYDOWN", MB_OK);return 0;}*/case WM_CHAR:{sprintf(szBuffer, "你按下了%c鍵\x00", wParam);MessageBox(NULL, szBuffer, "WM_CHAR", MB_OK);return 0;}}return DefWindowProc(hwnd, uMsg, wParam, lParam); }int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd ){//窗口的類名TCHAR className[] = "My First Window";//創建一個自己的窗口WNDCLASS wndclass = {0};wndclass.hbrBackground = (HBRUSH)COLOR_MENU;wndclass.lpfnWndProc = WindowProc;wndclass.lpszClassName = className;wndclass.hInstance = hInstance;//注冊RegisterClass(&wndclass);//創建窗口HWND hwnd = CreateWindow(className,TEXT("測試窗口"),WS_OVERLAPPEDWINDOW,10,10,600,300,NULL,NULL,hInstance,NULL);if(hwnd == NULL)return 0;//顯示窗口ShowWindow(hwnd, SW_SHOW);//消息循環MSG msg;while(GetMessage(&msg, NULL, 0, 0)){TranslateMessage(&msg);DispatchMessage(&msg);}return 0; }運行結果:
第二步:敲擊不同按鍵
1)小寫模式,按下"A":
2)按下大小寫鍵,切換到大寫模式:
無彈窗
3)大寫模式,按下”A“:
4)按下shift鍵:
無彈窗
5)小寫模式,按下shift+“A”:
第三步:修改代碼并重新運行程序
新代碼:
#include <stdio.h> #include <windows.h>LRESULT CALLBACK WindowProc(IN HWND hwnd,IN UINT uMsg,IN WPARAM wParam,IN LPARAM lParam ){TCHAR szBuffer[MAX_PATH];switch(uMsg){case WM_KEYDOWN:{sprintf(szBuffer, "鍵%d被你按下了\x00", wParam);MessageBox(NULL, szBuffer, "WM_KEYDOWN", MB_OK);return 0;}/*case WM_CHAR:{sprintf(szBuffer, "你按下了%c鍵\x00", wParam);MessageBox(NULL, szBuffer, "WM_CHAR", MB_OK);return 0;}*/}return DefWindowProc(hwnd, uMsg, wParam, lParam); }int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd ){//窗口的類名TCHAR className[] = "My First Window";//創建一個自己的窗口WNDCLASS wndclass = {0};wndclass.hbrBackground = (HBRUSH)COLOR_MENU;wndclass.lpfnWndProc = WindowProc;wndclass.lpszClassName = className;wndclass.hInstance = hInstance;//注冊RegisterClass(&wndclass);//創建窗口HWND hwnd = CreateWindow(className,TEXT("測試窗口"),WS_OVERLAPPEDWINDOW,10,10,600,300,NULL,NULL,hInstance,NULL);if(hwnd == NULL)return 0;//顯示窗口ShowWindow(hwnd, SW_SHOW);//消息循環MSG msg;while(GetMessage(&msg, NULL, 0, 0)){//TranslateMessage(&msg);DispatchMessage(&msg);}return 0; }運行結果:
第四步:敲擊不同按鍵
1)小寫模式,按下"A":
2)按下大小寫鍵,切換到大寫模式:
3)大寫模式,按下”A“:
4)按下shift鍵:
總結
總結
以上是生活随笔為你收集整理的Windows消息机制学习笔记(三)—— 消息的接收与分发的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 硬编码学习笔记(二)—— 经典变长指令
- 下一篇: 消息机制学习笔记(四)—— 内核回调机制