天天看點

Windows消息機制學習筆記(一)—— 消息隊列

Windows消息機制學習筆記(一)—— 消息隊列

    • 基本概念
    • 實驗一:使用代碼畫出最簡單視窗
      • 第一步:編譯并運作以下代碼
      • 第二步:檢視運作結果
      • 第三步:使用其它視窗對其進行覆寫,觀察效果
      • 總結
    • 消息隊列
      • 消息隊列在哪
        • Linux:專用程序
        • Windows:GUI線程
      • Win32Thread
    • 總結

基本概念

接觸過程式設計的人,或多或少用到過消息機制,但大多數人(包括我自己)隻是知道相關API的基本用法,卻不知道它是如何實作的

從本章起,我們将帶着以下幾個問題一起來學習消息機制:

  1. 什麼是視窗句柄?在哪裡?有什麼用?
  2. 什麼是消息?什麼是消息隊列?消息隊列在哪?
  3. 什麼是視窗過程?視窗過程是誰調用的?沒有消息循環視窗過程會執行嗎?
  4. 為什麼要有w32k.sys這個子產品?
  5. 為什麼隻有使用圖形界面的程式才可以通路KeServiceDescriptorTableShadow?
  6. 界面"卡死"的時候為什麼滑鼠還可以動?

實驗一:使用代碼畫出最簡單視窗

第一步:編譯并運作以下代碼

#include <stdio.h>
#define _WIN32_WINNT 0x500
#include <windows.h>

typedef struct _Color
{
	DWORD r;
	DWORD g;
	DWORD b;
}Color;

typedef struct _WindowClass
{
	DWORD x;
	DWORD y;
	DWORD width;
	DWORD height;
	Color color;
}WindowClass;

//按照WindowClass的參數,将hdc中的資料列印到指定裝置
void PaintWindows(HDC hdc, WindowClass *p)
{
	HBRUSH hBrush;
	hBrush = (HBRUSH)GetStockObject(DC_BRUSH);

	SelectObject(hdc, hBrush);			//畫刷
	SetDCBrushColor(hdc, RGB(p->color.r, p->color.g, p->color.b));

	MoveToEx(hdc, p->x, p->y, NULL);
	LineTo(hdc, p->x+p->width, p->y);
	LineTo(hdc, p->x+p->width, p->y+p->height);
	LineTo(hdc, p->x, p->y+p->height);
	LineTo(hdc, p->x, p->y);
	Rectangle(hdc, p->x, p->y, p->width, p->height+1);

	DeleteObject(hBrush);
}

int main()
{
	char cMessage;		//消息
	HWND hwnd;			//畫在哪
	HDC hdc;			//顯示卡緩存

	//設定視窗參數,長寬高之類的
	WindowClass wClass;
	wClass.x = 0;
	wClass.y = 0;
	wClass.width = 800;
	wClass.height = 400;
	wClass.color.r = 0xEF;
	wClass.color.g = 0xEB;
	wClass.color.b = 0xDE;

	//畫在哪
	hwnd = GetDesktopWindow();
	//hwnd = FindWindow("dbgviewClass", NULL);

	//擷取DC裝置句柄:可以把DC了解成顯示卡緩存
	hdc = GetWindowDC(hwnd);

	for(;;)
	{
		//畫視窗
		PaintWindows(hdc, &wClass);

		cMessage = getchar();
		switch(cMessage)
		{
		case 'a':
			wClass.color.r += 0x10;
			wClass.color.g += 0x10;
			wClass.color.b += 0x10;
			break;
		case 'b':
			wClass.color.r -= 0x10;
			wClass.color.g -= 0x10;
			wClass.color.b -= 0x10;
			break;
		}
	}

	return 0;
}
           

第二步:檢視運作結果

Windows消息機制學習筆記(一)—— 消息隊列

第三步:使用其它視窗對其進行覆寫,觀察效果

Windows消息機制學習筆記(一)—— 消息隊列

總結

  1. 畫出的部分被其它視窗覆寫後就消失了
  2. 手動畫出的視窗隻能接收鍵盤發送的消息

思考:如何使視窗能夠接收所有消息

答案:将所有消息放入一塊記憶體中,這塊記憶體被稱之為“消息隊列”

消息隊列

描述:本質上是一種資料結構,當對象接收到消息時,将接收到的所有消息放入消息隊列中,等待對象進行處理

規則:先進先出

Windows消息機制學習筆記(一)—— 消息隊列

消息隊列在哪

Linux:專用程序

  1. 使用專用程序捕獲所有消息
  2. 判斷消息所屬程序,進行分發,将消息配置設定到目标程序的消息隊列中
    Windows消息機制學習筆記(一)—— 消息隊列

Windows:GUI線程

檢視KTHREAD結構體

kd> dt _KTHREAD
ntdll!_KTHREAD
   ...
   +0x130 Win32Thread	//若目前程式為控制台程式且無使用任何圖形界面相關API,該成員為空
   						//若目前程式使用了圖形界面相關的API,該成員指向一個結構體,該結構體包含了消息隊列
   ...
           

GUI:微軟提供的與圖形界面相關的API,稱為GUI

GDI:自定義的用于圖形界面相關的API,稱為GDI

GUI線程:

  1. 當線程剛建立的時候,都是普通線程:

    Thread.ServiceTable

    指向

    KeServiceDescriptorTable

  2. 當線程第一次調用Win32k.sys(調用号大于0x100)時,會調用一個函數,将目前線程轉換成GUI線程:

    PsConvertToGuiThread

    主要做幾件事:

    a. 擴充核心棧,必須換成64KB的大核心棧,因為普通核心棧隻有12KB大小

    b.建立一個包含消息隊列的結構體,并挂到KTHREAD上

    c.将

    Thread.ServiceTable

    指向

    KeServiceDescriptorTableShadow

    (隻有當調用GUI時,才會指向SSDTShadow)

    d.把需要的記憶體資料映射到本程序空間

Win32Thread

描述:位于KTHREAD,若目前程式使用了圖形界面相關的API,該成員指向一個結構體,其中包含了目前線程的消息隊列:THREADINFO

//FROM ReactOS v0.4.13
typedef struct _THREADINFO{
	...
    struct _USER_MESSAGE_QUEUE* MessageQueue;	//消息隊列
    ...
} THREADINFO;
           

總結

  1. 消息隊列存儲在0環,通過KTHREAD.Win32Thread可以找到
  2. 并不是所有線程都要消息隊列,隻有GUI線程才有消息隊列
  3. 一個GUI線程對應1個消息隊列

繼續閱讀