天天看点

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个消息队列

继续阅读