Windows消息机制学习笔记(一)—— 消息队列
基本概念
接触过编程的人,或多或少用到过消息机制,但大多数人(包括我自己)只是知道相关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个消息队列