1.消息机制
消息隊列
typedef struct _Color {DWORD r,g,b; }Color;typedef struct _WindowClass {DWORD x;DWORD y;DWORD width;DWORD hight;Color color; }WindowClass;//畫窗口 void PaintWindows(HDC hdc,WindowClass* Win) {//取圖形對象HBRUSH hBrush = (HBRUSH)GetStockObject(DC_BRUSH);SelectObject(hdc,hBrush);//畫刷SetDCBrushColor(hdc,RGB(Win->color.r, Win->color.g, Win->color.b));MoveToEx(hdc,Win->x,Win->y,NULL);LineTo(hdc,Win->x+Win->width,Win->y);LineTo(hdc, Win->x+Win->width,Win->y+Win->hight);LineTo(hdc, Win->x, Win->y+Win->hight);LineTo(hdc, Win->x, Win->y);Rectangle(hdc,Win->x,Win->y,Win->x+Win->width,Win->y+Win->hight+1);DeleteObject(hBrush); }void main() {char Message;HWND hwnd;HDC hdc;//設置窗口參數WindowClass win;win.x = 10;win.y = 10;win.width = 300;win.hight = 300;win.color.r = 0x10;win.color.g = 0x20;win.color.b = 0x30;hwnd = GetDesktopWindow();//獲取桌面句柄hdc = GetWindowDC(hwnd);while (true){PaintWindows(hdc, &win);//接收消息Message = getchar();switch (Message){case 'a':win.color.r += 0x10;win.color.g += 0x20;win.color.b += 0x30;break;case 'b':win.color.r -= 0x10;win.color.g -= 0x20;win.color.b -= 0x30;break;}}getchar(); }Windows所有圖像界面編程的底層實現都是通過Gdi畫出來的。
但我們通過微軟提供的Api來創建窗口可以接收到,鼠標、鍵盤、或者其他進程發送的消息等等,上面的代碼只能接收到鍵盤的消息。
唯一的解決分案就是給它提供一塊內存,保所有接收到的消息都往這塊內存里放。
消息隊列
KTHREAD.Win32Thread如果你的進程調用了圖形界面的api這個成員會指向_THREADINFO結構體,這個結構體的MessageQueue成員就是“消息隊列”,
沒調用圖形界面的api, Win32Thread就為NULL。
GUI線程
<1> 當線程剛創建的時候,都是普通線程:
Thread.ServiceTable-> KeServiceDescriptorTable(ssdt)
<2> 當線程第一次調用Win32k.sys時,會調用一個函數:PsConvertToGuiThread //當前線程轉為GUI線程
主要做幾件事:
每一個GUI線程對應1個消息隊列
普通線程是看不了SSSDT表的,調用了win32k里函數的線程才能看到SSSDT表。
窗口與線程
消息是程序發送的,可以通過GetMessage獲取。
點擊關閉后產生消息,這消息會存儲到對應窗口的線程的消息隊列中。
調用w32k的模塊會先調用InitInputImpl()初始化,這函數啟動了兩條線程,一條用來監控鍵盤,一條監控鼠標
某些原因導致程序突然卡死了,但鼠標還可以動,這是因為鼠標是有著自己獨立的線程
創建窗口過程:
user32.dll CreateWindowEx
1. CreateWindowEx()2. _VerNtUserCreateWindowEx()3. _NtUserCreateWindowEx() 這里開始進入0環 窗口對象: _WINDOW_OBJECT ... PTHREADINFO pti; //所屬線程 ...創建窗口要提供一個窗口過程(回調函數)WNDPROC lpfnWndProc
Windows建立了一張表,所有窗口對應的結構體地址都在里面,返回3環只是返回了索引
窗口句柄是全局的,窗口創建后句柄就已經確定,你在別的進程中通過FindWindow獲取它得到的值是一樣的。
消息的接收
創建窗口就是在0環創建_WINDOW_OBJECT結構體,然后賦上該有的值
MSG msg; while(GetMessage(&msg,NULL,0,0)) {TranslateMessage(&msg); //翻譯消息DispatchMessage(&msg); //分發消息 } GetMessage(LPMSG lpMsg, //返回從隊列中摘下來的消息HWND hWnd, //過濾條件一:發個這個窗口的消息UNIT wMsgFilterMin, //過濾條件UNIT wMsgFilterMax //過濾條件 ); GetMessage的主要功能: 循環判斷是否有該窗口的消息,如果有,將消息存儲到MSG指定的結構,并將消息從列表中刪除。 還會處理掉SentMessages()發送來的消息MSG msg; while(GetMessage(&msg,NULL,0,0)) {//TranslateMessage(&msg); //翻譯消息//DispatchMessage(&msg); //分發消息 } 也就是說就是你將這里面兩行注釋掉一樣會處理SentMessagesListHead里的消息 消息隊列的結構 1. SentMessagesListHead //接到SendMessage發來的消息 2. PostedMessagesListHead //接到PostMessage發來的消息 3. HardwareMessagesListHead //接到鼠標、鍵盤的消息 略... 消息隊列有7組,我這只列出了3組 NtUserGetMessage的執行流程: User32!GetMessage() 調用 w32k!NtUserGetMessage()co_IntGetPeekMessage() co_IntPeekMessage() co_MsqDispatchOneSentMessage() co_IntCallSentMessageCallback() KeUserModeCallback()//返回3環調用3環窗口過程do {//先判斷SentMessagesListHead是否有消息 如果有處理掉do{....KeUserModeCallback(USER32_CALLBACK_WINDOWPROC,Arguments,ArgumentLength,&ResultPointer,&ResultLength);....}while(SentMessagesListHead != NULL)//依次判斷其他的6個隊列,里面如果有消息 返回 沒有繼續 }while(其他隊列!=NULL)詳細版偽代碼和上那個一樣的
co_IntCallSentMessageCallback(SENDASYNCPROC CompletionCallback,HWND hWnd,UINT Msg,ULONG_PTR CompletionCallbackContext,LRESULT Result) {略......KeUserModeCallback(USER32_CALLBACK_SENDASYNCPROC,&Arguments,sizeof(SENDASYNCPROC_CALLBACK_ARGUMENTS),&ResultPointer,&ResultLength);略...... }co_MsqDispatchOneSentMessage(_In_ PTHREADINFO pti) {略......co_IntCallSentMessageCallback(Message->CompletionCallback,Message->Msg.hwnd,Message->Msg.message,Message->CompletionCallbackContext,Message->lResult);略...... }co_IntPeekMessage(PMSG Msg,PWND Window,UINT MsgFilterMin,UINT MsgFilterMax,UINT RemoveMsg,LONG_PTR *ExtraInfo,BOOL bGMSG ) {略......do{略......while ( co_MsqDispatchOneSentMessage(pti) ){if (HIWORD(RemoveMsg) && !bGMSG) Hit = TRUE;}略......}while(SentMessagesListHead != NULL)略..... }co_IntGetPeekMessage(PMSG pMsg,HWND hWnd,UINT MsgFilterMin,UINT MsgFilterMax,UINT RemoveMsg,BOOL bGMSG ) {略......do{Present = co_IntPeekMessage( pMsg,Window,MsgFilterMin,MsgFilterMax,RemoveMsg,&ExtraInfo,bGMSG );略......//依次判斷其他的6個隊列,里面如果有消息 返回 沒有繼續}while(其他隊列!=NULL)略...... }SendMessage與PostMessage的區別(同步與異步):
1. 同步 SendMessage()發送消息,GetMessage();接收,進入0環要遍歷SentMessagesListHead有沒有消息, 有就處理,沒有就返回, 必須要處理完才會返回結果,SendMessage()要接收到結果才會結束否則會一直 堵塞 在這2. 異步 PostMessage()發送消息,GetMessage()只會接收它的消息,不會處理, 它的消息由TranslateMessage(&msg)與DispatchMessage(&msg)來處理PostMessage()發送完后是不會等待你的處理結果的,發完立馬就結束消息的分發
MSG msg; while(GetMessage(&msg,NULL,0,0)) {TranslateMessage(&msg); //翻譯消息 (處理鍵盤碼的)DispatchMessage(&msg); //分發消息 根據窗口句柄調用窗口過程 }一個線程可以有多個窗口(按鈕之類的控件也算窗口),它們共享著一個消息隊列, GetMessage()會把這個線程的所有消息都取出來,所以叫“分發消息”TranslateMessage(&msg); //翻譯消息
如果注釋掉TranslateMessage(&msg)它就只能接收到鍵盤碼 case WH_KEYDOWN: {sprintf(szBuffer,"你按下了%d鍵",wParam);MessageBox(hwnd,szBuffer,NULL,MB_OK); }加了TranslateMessage(&msg)它就能幫你翻譯為字符 case WH_CHAR: {sprintf(szBuffer,"你按下了%c鍵",wParam);MessageBox(hwnd,szBuffer,NULL,MB_OK); }其他隊列的處理流程:
1. User32!DispatchMessage() 調用 w32k!NtUserDispatchMessage() 2. IntDispatchMessage() 3. co_IntCallWindowProc() 4. KeUserModeCallback() //返回3環調用3環窗口過程<1> 根據窗口句柄找到窗口對象 <2> 根據窗口對象得到窗口過程函數,由0環發起調用在回調函數里我們只處理我們需要的消息,別的消息給默認的窗口過程處理函數來處理
//默認窗口過程處理函數 return DefWindowProc(hWnd, uMsg, wParam, lParam);總結