天天看點

Windows消息機制學習筆記(二)—— 視窗與線程

Windows消息機制學習筆記(二)—— 視窗與線程

    • 要點回顧
    • 消息從哪裡來?
      • 實驗一:Spy++捕獲消息
      • 實驗二:消息捕獲
    • 消息到哪裡去?
    • 視窗在哪?
      • 實驗:分析CreateWindowExW
    • 視窗對象
    • 總結

要點回顧

一個GUI線程對應一個消息隊列

本篇要解決的問題:

  1. 消息從哪裡來?
  2. 消息到哪裡去?
  3. 誰來做這些事情?

消息從哪裡來?

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;
}
           

運作結果

Windows消息機制學習筆記(二)—— 視窗與線程

2)使用spy++定位視窗

Windows消息機制學習筆記(二)—— 視窗與線程
Windows消息機制學習筆記(二)—— 視窗與線程
Windows消息機制學習筆記(二)—— 視窗與線程
Windows消息機制學習筆記(二)—— 視窗與線程
Windows消息機制學習筆記(二)—— 視窗與線程

3)在視窗中進行任意操作,例如滑鼠移動,滑鼠點選,鍵盤敲擊等,觀察消息清單

Windows消息機制學習筆記(二)—— 視窗與線程

實驗二:消息捕獲

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;
}
           

執行結果

Windows消息機制學習筆記(二)—— 視窗與線程

消息到哪裡去?

描述:

  1. 當我們使用滑鼠某個視窗進行點選與滑動時,都會産生一個消息,消息會進入目前視窗對應線程的消息隊列中
  2. 當我們編寫程式時,并不會去特地啟動兩個線程去監控滑鼠和鍵盤,w32k.sys負責了這個事情
    Windows消息機制學習筆記(二)—— 視窗與線程

當初始化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中

Windows消息機制學習筆記(二)—— 視窗與線程
Windows消息機制學習筆記(二)—— 視窗與線程
Windows消息機制學習筆記(二)—— 視窗與線程
Windows消息機制學習筆記(二)—— 視窗與線程

總結:視窗在0環被畫出(由w32k.sys實作)

視窗對象

描述:

  1. 每個視窗對應一個WINDOW_OBJECT結構體,位于0環,包含目前視窗所有資訊
  2. 除了大視窗之外,視窗中的每個控件也都是一個視窗,也分别對應一個WINDOW_OBJECT結構體
  3. 一個視窗内包含着許多個視窗,按鈕,對話框也都屬于視窗,屬于同一個線程
  4. 一個線程可以包含多個視窗,但每個視窗隻能屬于一個線程
//FROM ReactOS v3.12
typedef struct _WINDOW_OBJECT
{
	...
	PWND Wnd;			//包含大量視窗資訊
	PTHREADINFO pti;	//線程
	...
}
           
//FROM ReactOS v3.12
typedef struct _WND
{
    ...
    /* Style. */
    DWORD style;		//包含視窗風格/後一個視窗/前一個視窗/父視窗/子視窗等資訊
    ...
} WND, *PWND;
           

消息進入視窗消息隊列的過程:

  1. 當使用滑鼠在某個視窗上點選時,滑鼠監控線程檢測到點選的視窗對象
  2. 根據視窗對象成員,找到視窗對應線程
  3. 将消息放入該線程的消息隊列中

注意:在3環得到的視窗的句柄隻是一個索引(參考句柄表章節)

總結

  1. 視窗在0環建立
  2. 視窗句柄是全局的
  3. 一個線程可以使用多個視窗,但每個視窗隻能屬于一個線程

繼續閱讀