Windows消息机制学习笔记(三)—— 消息的接收与分发
Windows消息機(jī)制學(xué)習(xí)筆記(三)—— 消息的接收與分發(fā)
- 要點(diǎn)回顧
- 消息循環(huán)
- 消息隊(duì)列
- 消息的接收
- GetMessage
- 實(shí)驗(yàn)1:理解GetMessage
- 第一步:編譯并運(yùn)行程序A
- 第二步:編譯并運(yùn)行程序B
- 同步與異步
- 實(shí)驗(yàn)2:理解同步與異步
- 第一步:編譯并運(yùn)行實(shí)驗(yàn)1中的程序A
- 第二步:編譯并運(yùn)行實(shí)驗(yàn)1中的程序B
- 第三步:點(diǎn)擊確定按鈕
- 第四步:修改程序B的代碼,再次運(yùn)行
- 消息的分發(fā)
- DispatchMessage
- 實(shí)驗(yàn)3:理解DispatchMessage
- 第一步:編譯并運(yùn)行以下代碼
- 第二步:操作窗口
- 第三步:去掉DispatchMessage注釋,重新運(yùn)行
- 第四步:再次操作窗口
- TranslateMessage
- 實(shí)驗(yàn)4:理解TranslateMessage
- 第一步:編譯并運(yùn)行以下代碼
- 第二步:敲擊不同按鍵
- 第三步:修改代碼并重新運(yùn)行程序
- 第四步:敲擊不同按鍵
- 總結(jié)
要點(diǎn)回顧
1)一個(gè)GUI線程包含一個(gè)消息隊(duì)列
普通線程 ↓ GUI線程 ↓ THEWAD.W32THREAD ↓ THREADINFO ↓ 消息隊(duì)列2)一個(gè)線程可以包含多個(gè)窗口,所有窗口共享一個(gè)消息隊(duì)列
_WINDOW_OBJECT //0環(huán)創(chuàng)建 ↓ PTHREADINFO pti //所屬線程 ↓ WNDPROC lpfnWndProc //窗口過(guò)程(窗口回調(diào)函數(shù))消息循環(huán)
MSG msg; while(GetMessage(&msg, NULL, 0, 0)) //從消息隊(duì)列中取出消息 {TranslateMessage(&msg); //加工消息DispatchMessage(&msg); //分發(fā)消息 }思考:使用SendMessage與PostMessage發(fā)送的消息位于同一組隊(duì)列中嗎?
答案:不同類(lèi)型的消息被置于不同隊(duì)列。
消息隊(duì)列
描述:消息隊(duì)列共有七組,用于存放不同類(lèi)型的消息。
例如:
完整隊(duì)列:
//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
描述:從消息隊(duì)列中取出消息
BOOL?WINAPI?GetMessage(LPMSG lpMsg, //返回從隊(duì)列中取出的消息HWND hWnd, //過(guò)濾條件一:窗口句柄UINT wMsgFilterMin, //過(guò)濾條件二:最小值UINT wMsgFilterMax //過(guò)濾條件三:最大值 );主要功能:循環(huán)判斷是否存在屬于該窗口的消息,若存在,則將消息存儲(chǔ)到MSG指定的結(jié)構(gòu)中,并將消息從列表中刪除。
注意:事實(shí)上,GetMessage還做了一件很重要的事情,即在接收消息時(shí),將SentMessagesListHead中的消息進(jìn)行處理
大致流程:
User32!GetMessage ↓ w32k!NtUsrGetMessage ↓ do {//先判斷SentMessagesListHead中是否存在消息,若存在則進(jìn)行處理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); //對(duì)應(yīng)外層循環(huán)... } BOOL FASTCALL co_IntPeekMessage( PUSER_MESSAGE Msg,PWINDOW_OBJECT Window,UINT MsgFilterMin,UINT MsgFilterMax,UINT RemoveMsg ) {.../* Dispatch sent messages here. */while (co_MsqDispatchOneSentMessage(ThreadQueue)); //對(duì)應(yīng)內(nèi)層循環(huán)... } BOOLEAN FASTCALL co_MsqDispatchOneSentMessage(PUSER_MESSAGE_QUEUE MessageQueue) {...if (Message->HookMessage == MSQ_ISHOOK){...}else if (Message->HookMessage == MSQ_ISEVENT){...}else{/* 發(fā)送消息 */Result = co_IntSendMessage(Message->Msg.hwnd,Message->Msg.message,Message->Msg.wParam,Message->Msg.lParam);}/* 從消息隊(duì)列中刪除該消息 */RemoveEntryList(&Message->ListEntry);.../* 調(diào)用回調(diào)函數(shù) */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) {.../* 回到三環(huán)進(jìn)行處理 */Status = KeUserModeCallback(USER32_CALLBACK_SENDASYNCPROC,&Arguments,sizeof(SENDASYNCPROC_CALLBACK_ARGUMENTS),&ResultPointer,&ResultLength);... }實(shí)驗(yàn)1:理解GetMessage
第一步:編譯并運(yùn)行程序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, "測(cè)試窗口接收到消息", "新消息", MB_OK);return false;}return DefWindowProc(hwnd, uMsg, wParam, lParam); }int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd ){//窗口的類(lèi)名TCHAR className[] = "My First Window";//創(chuàng)建一個(gè)自己的窗口WNDCLASS wndclass = {0};wndclass.hbrBackground = (HBRUSH)COLOR_MENU;wndclass.lpfnWndProc = WindowProc;wndclass.lpszClassName = className;wndclass.hInstance = hInstance;//注冊(cè)RegisterClass(&wndclass);//創(chuàng)建窗口HWND hwnd = CreateWindow(className,TEXT("測(cè)試窗口"),WS_OVERLAPPEDWINDOW,10,10,600,300,NULL,NULL,hInstance,NULL);if(hwnd == NULL)return 0;//顯示窗口ShowWindow(hwnd, SW_SHOW);//消息循環(huán)MSG msg;while(GetMessage(&msg, NULL, 0, 0)){//TranslateMessage(&msg);//DispatchMessage(&msg);}return 0; }運(yùn)行結(jié)果:
注意:不要關(guān)閉窗口
第二步:編譯并運(yùn)行程序B
#include <stdio.h> #include <windows.h>int main() {HWND hwnd = FindWindow("My First Window", "測(cè)試窗口");SendMessage(hwnd, 0x401, 0, 0);return 0; }運(yùn)行結(jié)果:
同步與異步
描述:
實(shí)驗(yàn)2:理解同步與異步
第一步:編譯并運(yùn)行實(shí)驗(yàn)1中的程序A
運(yùn)行結(jié)果:
第二步:編譯并運(yùn)行實(shí)驗(yàn)1中的程序B
在return處設(shè)置斷點(diǎn):
運(yùn)行結(jié)果:
此時(shí),程序B并未繼續(xù)向下執(zhí)行。
第三步:點(diǎn)擊確定按鈕
執(zhí)行結(jié)果:
程序A處理消息后,程序B繼續(xù)向下執(zhí)行。
第四步:修改程序B的代碼,再次運(yùn)行
新代碼:
#include <stdio.h> #include <windows.h>int main() {HWND hwnd = FindWindow("My First Window", "測(cè)試窗口");PostMessage(hwnd, 0x401, 0, 0); //SendMessage改為PostMessagereturn 0; }執(zhí)行結(jié)果:
程序B發(fā)送消息后未等待目標(biāo)處理繼續(xù)向下執(zhí)行,程序A并未處理消息。
消息的分發(fā)
描述:GetMessage能夠處理SentMessagesListHead隊(duì)列中的消息,其他隊(duì)列中的消息則由DispatchMessage進(jìn)行分發(fā)處理。
大致流程:
源碼(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) {.../* 回到三環(huán),調(diào)用窗口過(guò)程函數(shù) */Status = KeUserModeCallback(USER32_CALLBACK_WINDOWPROC,Arguments,ArgumentLength,&ResultPointer,&ResultLength);... }DispatchMessage
描述:用于消息分發(fā),根據(jù)窗口句柄調(diào)用相關(guān)的窗口過(guò)程,通常用于分發(fā)由GetMessage函數(shù)檢索到的消息。
LRESULT DispatchMessage(CONST MSG *lpmsg // message information );思考:為什么DispatchMessage要對(duì)消息進(jìn)行分發(fā)而不統(tǒng)一處理?
答案:所有窗口共享一個(gè)消息隊(duì)列,且每個(gè)窗口都擁有屬于自己的回調(diào)函數(shù)。
MSG結(jié)構(gòu)體成員:
typedef struct tagMSG {HWND hwnd; //窗口句柄UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; } MSG, *PMSG;DispatchMessage根據(jù)MSG->hwnd調(diào)用各句柄對(duì)應(yīng)窗口對(duì)象的回調(diào)函數(shù)。
思考:既然GetMessage已經(jīng)得到了句柄信息,為什么不能直接調(diào)用對(duì)應(yīng)的回調(diào)函數(shù)而需要分發(fā)。
答案:因?yàn)榫浔赶虻氖蔷浔?#xff0c;而不是窗口對(duì)象,需要進(jìn)入0環(huán)通過(guò)句柄表找到窗口對(duì)象,才能調(diào)用對(duì)應(yīng)的回調(diào)函數(shù)。
實(shí)驗(yàn)3:理解DispatchMessage
第一步:編譯并運(yùn)行以下代碼
#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, "測(cè)試窗口接收到消息", "新消息", 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 ){//窗口的類(lèi)名TCHAR className[] = "My First Window";//創(chuàng)建一個(gè)自己的窗口WNDCLASS wndclass = {0};wndclass.hbrBackground = (HBRUSH)COLOR_MENU;wndclass.lpfnWndProc = WindowProc;wndclass.lpszClassName = className;wndclass.hInstance = hInstance;//注冊(cè)RegisterClass(&wndclass);//創(chuàng)建窗口HWND hwnd = CreateWindow(className,TEXT("測(cè)試窗口"),WS_OVERLAPPEDWINDOW,10,10,600,300,NULL,NULL,hInstance,NULL);if(hwnd == NULL)return 0;//顯示窗口ShowWindow(hwnd, SW_SHOW);//消息循環(huán)MSG msg;while(GetMessage(&msg, NULL, 0, 0)){//TranslateMessage(&msg);//DispatchMessage(&msg);}return 0; }運(yùn)行結(jié)果:
第二步:操作窗口
拖動(dòng)窗口、點(diǎn)擊最小化、縮放、關(guān)閉按鈕均無(wú)反應(yīng)。
第三步:去掉DispatchMessage注釋,重新運(yùn)行
MSG msg; while(GetMessage(&msg, NULL, 0, 0)) {//TranslateMessage(&msg);DispatchMessage(&msg); }第四步:再次操作窗口
拖動(dòng)窗口、點(diǎn)擊最小化、縮放、關(guān)閉按鈕均得到相應(yīng)。
TranslateMessage
描述:用于將虛擬鍵碼轉(zhuǎn)換為字符消息。該字符消息又被發(fā)送給對(duì)應(yīng)線程(調(diào)用TranslateMessage的線程)的消息隊(duì)列,當(dāng)線程再次調(diào)用GetMessage函數(shù)或PeekMessage函數(shù)獲取消息的時(shí)候被讀取。
實(shí)驗(yàn)4:理解TranslateMessage
第一步:編譯并運(yùn)行以下代碼
#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 ){//窗口的類(lèi)名TCHAR className[] = "My First Window";//創(chuàng)建一個(gè)自己的窗口WNDCLASS wndclass = {0};wndclass.hbrBackground = (HBRUSH)COLOR_MENU;wndclass.lpfnWndProc = WindowProc;wndclass.lpszClassName = className;wndclass.hInstance = hInstance;//注冊(cè)RegisterClass(&wndclass);//創(chuàng)建窗口HWND hwnd = CreateWindow(className,TEXT("測(cè)試窗口"),WS_OVERLAPPEDWINDOW,10,10,600,300,NULL,NULL,hInstance,NULL);if(hwnd == NULL)return 0;//顯示窗口ShowWindow(hwnd, SW_SHOW);//消息循環(huán)MSG msg;while(GetMessage(&msg, NULL, 0, 0)){TranslateMessage(&msg);DispatchMessage(&msg);}return 0; }運(yùn)行結(jié)果:
第二步:敲擊不同按鍵
1)小寫(xiě)模式,按下"A":
2)按下大小寫(xiě)鍵,切換到大寫(xiě)模式:
無(wú)彈窗
3)大寫(xiě)模式,按下”A“:
4)按下shift鍵:
無(wú)彈窗
5)小寫(xiě)模式,按下shift+“A”:
第三步:修改代碼并重新運(yùn)行程序
新代碼:
#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 ){//窗口的類(lèi)名TCHAR className[] = "My First Window";//創(chuàng)建一個(gè)自己的窗口WNDCLASS wndclass = {0};wndclass.hbrBackground = (HBRUSH)COLOR_MENU;wndclass.lpfnWndProc = WindowProc;wndclass.lpszClassName = className;wndclass.hInstance = hInstance;//注冊(cè)RegisterClass(&wndclass);//創(chuàng)建窗口HWND hwnd = CreateWindow(className,TEXT("測(cè)試窗口"),WS_OVERLAPPEDWINDOW,10,10,600,300,NULL,NULL,hInstance,NULL);if(hwnd == NULL)return 0;//顯示窗口ShowWindow(hwnd, SW_SHOW);//消息循環(huán)MSG msg;while(GetMessage(&msg, NULL, 0, 0)){//TranslateMessage(&msg);DispatchMessage(&msg);}return 0; }運(yùn)行結(jié)果:
第四步:敲擊不同按鍵
1)小寫(xiě)模式,按下"A":
2)按下大小寫(xiě)鍵,切換到大寫(xiě)模式:
3)大寫(xiě)模式,按下”A“:
4)按下shift鍵:
總結(jié)
總結(jié)
以上是生活随笔為你收集整理的Windows消息机制学习笔记(三)—— 消息的接收与分发的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 硬编码学习笔记(二)—— 经典变长指令
- 下一篇: 消息机制学习笔记(四)—— 内核回调机制