Windows消息機制學習筆記(一)—— 消息隊列
-
- 基本概念
- 實驗一:使用代碼畫出最簡單視窗
-
- 第一步:編譯并運作以下代碼
- 第二步:檢視運作結果
- 第三步:使用其它視窗對其進行覆寫,觀察效果
- 總結
- 消息隊列
-
- 消息隊列在哪
-
- Linux:專用程序
- Windows:GUI線程
- Win32Thread
- 總結
基本概念
接觸過程式設計的人,或多或少用到過消息機制,但大多數人(包括我自己)隻是知道相關API的基本用法,卻不知道它是如何實作的
從本章起,我們将帶着以下幾個問題一起來學習消息機制:
- 什麼是視窗句柄?在哪裡?有什麼用?
- 什麼是消息?什麼是消息隊列?消息隊列在哪?
- 什麼是視窗過程?視窗過程是誰調用的?沒有消息循環視窗過程會執行嗎?
- 為什麼要有w32k.sys這個子產品?
- 為什麼隻有使用圖形界面的程式才可以通路KeServiceDescriptorTableShadow?
- 界面"卡死"的時候為什麼滑鼠還可以動?
實驗一:使用代碼畫出最簡單視窗
第一步:編譯并運作以下代碼
#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;
}
第二步:檢視運作結果
第三步:使用其它視窗對其進行覆寫,觀察效果
總結
- 畫出的部分被其它視窗覆寫後就消失了
- 手動畫出的視窗隻能接收鍵盤發送的消息
思考:如何使視窗能夠接收所有消息
答案:将所有消息放入一塊記憶體中,這塊記憶體被稱之為“消息隊列”
消息隊列
描述:本質上是一種資料結構,當對象接收到消息時,将接收到的所有消息放入消息隊列中,等待對象進行處理
規則:先進先出
消息隊列在哪
Linux:專用程序
- 使用專用程序捕獲所有消息
- 判斷消息所屬程序,進行分發,将消息配置設定到目标程序的消息隊列中
Windows:GUI線程
檢視KTHREAD結構體
kd> dt _KTHREAD
ntdll!_KTHREAD
...
+0x130 Win32Thread //若目前程式為控制台程式且無使用任何圖形界面相關API,該成員為空
//若目前程式使用了圖形界面相關的API,該成員指向一個結構體,該結構體包含了消息隊列
...
GUI:微軟提供的與圖形界面相關的API,稱為GUI
GDI:自定義的用于圖形界面相關的API,稱為GDI
GUI線程:
- 當線程剛建立的時候,都是普通線程:
指向Thread.ServiceTable
KeServiceDescriptorTable
- 當線程第一次調用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;
總結
- 消息隊列存儲在0環,通過KTHREAD.Win32Thread可以找到
- 并不是所有線程都要消息隊列,隻有GUI線程才有消息隊列
- 一個GUI線程對應1個消息隊列