Windows消息機制學習筆記(二)—— 視窗與線程
-
- 要點回顧
- 消息從哪裡來?
-
- 實驗一:Spy++捕獲消息
- 實驗二:消息捕獲
- 消息到哪裡去?
- 視窗在哪?
-
- 實驗:分析CreateWindowExW
- 視窗對象
- 總結
要點回顧
一個GUI線程對應一個消息隊列
本篇要解決的問題:
- 消息從哪裡來?
- 消息到哪裡去?
- 誰來做這些事情?
消息從哪裡來?
1)Spy++ 捕捉消息:滑鼠消息、鍵盤消息
2)程式發送消息
實驗一:Spy++捕獲消息
1)編譯并運作以下代碼
#include <windows.h>
LRESULT CALLBACK WindowProc(
IN HWND hwnd,
IN UINT uMsg,
IN WPARAM wParam,
IN LPARAM lParam
){
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;
}
運作結果
2)使用spy++定位視窗
3)在視窗中進行任意操作,例如滑鼠移動,滑鼠點選,鍵盤敲擊等,觀察消息清單
實驗二:消息捕獲
1)程序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;
}
2)程序B運作以下代碼
#include <stdio.h>
#include <windows.h>
int main()
{
HWND hwnd = FindWindow("My First Window", "我的第一個視窗");
SendMessage(hwnd, 0x401, 0, 0);
return 0;
}
執行結果
消息到哪裡去?
描述:
- 當我們使用滑鼠某個視窗進行點選與滑動時,都會産生一個消息,消息會進入目前視窗對應線程的消息隊列中
- 當我們編寫程式時,并不會去特地啟動兩個線程去監控滑鼠和鍵盤,w32k.sys負責了這個事情
當初始化w32k.sys這個子產品時,會調用一個叫做InitInputImpl的函數
這個函數會啟動兩個線程,分别用來監控滑鼠和鍵盤,這兩個線程都是0環的線程
平時我們的電腦遭遇“當機”時,常常是螢幕動不了,滑鼠還能動,這正式由于滑鼠是有一個獨立的線程在監控它的行動
//FROM ReactOS v3.12
InitInputImpl(VOID)
{
...
Status = PsCreateSystemThread(&RawInputThreadHandle, //監控滑鼠
THREAD_ALL_ACCESS,
NULL,
NULL,
&RawInputThreadId,
RawInputThreadMain,
NULL);
if (!NT_SUCCESS(Status))
{
DPRINT1("Win32K: Failed to create raw thread.\n");
}
Status = PsCreateSystemThread(&KeyboardThreadHandle, //監控鍵盤
THREAD_ALL_ACCESS,
NULL,
NULL,
&KeyboardThreadId,
KeyboardThreadMain,
NULL);
if (!NT_SUCCESS(Status))
{
DPRINT1("Win32K: Failed to create keyboard thread.\n");
}
...
}
思考:w32k.sys如何區分使用者點選的是哪個視窗/消息要去哪個線程
視窗在哪?
當調用CreateWindow時,該函數實際上是一個宏,其CreateWindowA實際對應CreateWindowExA函數,CreateWindowW對應CreateWindowExW函數,可在編輯器中跟蹤觀察
實驗:分析CreateWindowExW
函數位于user32.dll中
總結:視窗在0環被畫出(由w32k.sys實作)
視窗對象
描述:
- 每個視窗對應一個WINDOW_OBJECT結構體,位于0環,包含目前視窗所有資訊
- 除了大視窗之外,視窗中的每個控件也都是一個視窗,也分别對應一個WINDOW_OBJECT結構體
- 一個視窗内包含着許多個視窗,按鈕,對話框也都屬于視窗,屬于同一個線程
- 一個線程可以包含多個視窗,但每個視窗隻能屬于一個線程
//FROM ReactOS v3.12
typedef struct _WINDOW_OBJECT
{
...
PWND Wnd; //包含大量視窗資訊
PTHREADINFO pti; //線程
...
}
//FROM ReactOS v3.12
typedef struct _WND
{
...
/* Style. */
DWORD style; //包含視窗風格/後一個視窗/前一個視窗/父視窗/子視窗等資訊
...
} WND, *PWND;
消息進入視窗消息隊列的過程:
- 當使用滑鼠在某個視窗上點選時,滑鼠監控線程檢測到點選的視窗對象
- 根據視窗對象成員,找到視窗對應線程
- 将消息放入該線程的消息隊列中
注意:在3環得到的視窗的句柄隻是一個索引(參考句柄表章節)
總結
- 視窗在0環建立
- 視窗句柄是全局的
- 一個線程可以使用多個視窗,但每個視窗隻能屬于一個線程