1.消息机制

消息队列

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

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

//画窗口
void PaintWindows(HDC hdc,WindowClass* Win)
{
	//取图形对象
	HBRUSH hBrush = (HBRUSH)GetStockObject(DC_BRUSH);

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

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

	DeleteObject(hBrush);
}

void main()
{
	char Message;
	HWND hwnd;
	HDC hdc;

	//设置窗口参数
	WindowClass win;
	win.x = 10;
	win.y = 10;
	win.width = 300;
	win.hight = 300;
	win.color.r = 0x10;
	win.color.g = 0x20;
	win.color.b = 0x30;

	hwnd = GetDesktopWindow();//获取桌面句柄
	hdc = GetWindowDC(hwnd);

	while (true)
	{
		PaintWindows(hdc, &win);

		//接收消息
		Message = getchar();
		switch (Message)
		{
		case 'a':
			win.color.r += 0x10;
			win.color.g += 0x20;
			win.color.b += 0x30;
			break;

		case 'b':
			win.color.r -= 0x10;
			win.color.g -= 0x20;
			win.color.b -= 0x30;
			break;
		}

	}

	getchar();
}

Windows所有图像界面编程的底层实现都是通过Gdi画出来的。

但我们通过微软提供的Api来创建窗口可以接收到,鼠标、键盘、或者其他进程发送的消息等等,上面的代码只能接收到键盘的消息。

唯一的解决分案就是给它提供一块内存,保所有接收到的消息都往这块内存里放。

消息队列

在这里插入图片描述

KTHREAD.Win32Thread如果你的进程调用了图形界面的api这个成员会指向_THREADINFO结构体,这个结构体的MessageQueue成员就是“消息队列”,

没调用图形界面的api, Win32Thread就为NULL。

GUI线程

<1> 当线程刚创建的时候,都是普通线程:
Thread.ServiceTable-> KeServiceDescriptorTable(ssdt)

<2> 当线程第一次调用Win32k.sys时,会调用一个函数:PsConvertToGuiThread //当前线程转为GUI线程

主要做几件事:

  1. 扩充内核栈,必须换成64KB的大内核栈,因为普通内核栈只有12KB大小。
  2. 创建_THREADINFO结构体,这结构体包含消息队列,并挂到KTHREAD上。
  3. Thread.ServiceTable-> KeServiceDescriptorTableShadow
  4. 把需要的内存数据映射到本进程空间

每一个GUI线程对应1个消息队列

普通线程是看不了SSSDT表的,调用了win32k里函数的线程才能看到SSSDT表。

窗口与线程

消息是程序发送的,可以通过GetMessage获取。

当你点击程序右上角的X后会经过:
点击关闭后产生消息,这消息会存储到对应窗口的线程的消息队列中。

调用w32k的模块会先调用InitInputImpl()初始化,这函数启动了两条线程,一条用来监控键盘,一条监控鼠标

某些原因导致程序突然卡死了,但鼠标还可以动,这是因为鼠标是有着自己独立的线程

创建窗口过程:

user32.dll CreateWindowEx
 1. CreateWindowEx()
 2. _VerNtUserCreateWindowEx()
 3. _NtUserCreateWindowEx()    这里开始进入0
窗口对象:
_WINDOW_OBJECT
...	
PTHREADINFO pti;	//所属线程
...

创建窗口要提供一个窗口过程(回调函数)WNDPROC lpfnWndProc

Windows建立了一张表,所有窗口对应的结构体地址都在里面,返回3环只是返回了索引

窗口句柄是全局的,窗口创建后句柄就已经确定,你在别的进程中通过FindWindow获取它得到的值是一样的。

  1. 窗口是在0环创建的
  2. 窗口句柄是全局的
  3. 一个线程可以用多个窗口,但每个窗口只能属于一个线程

消息的接收

在这里插入图片描述

创建窗口就是在0环创建_WINDOW_OBJECT结构体,然后赋上该有的值

MSG msg;
while(GetMessage(&msg,NULL,0,0))
{
	TranslateMessage(&msg); //翻译消息
	DispatchMessage(&msg);  //分发消息
}
GetMessage(LPMSG lpMsg,	//返回从队列中摘下来的消息
		HWND hWnd,		//过滤条件一:发个这个窗口的消息
		UNIT wMsgFilterMin,	//过滤条件
		UNIT wMsgFilterMax	//过滤条件
);
GetMessage的主要功能:
循环判断是否有该窗口的消息,如果有,将消息存储到MSG指定的结构,并将消息从列表中删除。
还会处理掉SentMessages()发送来的消息

MSG msg;
while(GetMessage(&msg,NULL,0,0))
{
	//TranslateMessage(&msg); //翻译消息
	//DispatchMessage(&msg);  //分发消息
}
也就是说就是你将这里面两行注释掉一样会处理SentMessagesListHead里的消息
消息队列的结构
1. SentMessagesListHead	//接到SendMessage发来的消息
2. PostedMessagesListHead	//接到PostMessage发来的消息
3. HardwareMessagesListHead	//接到鼠标、键盘的消息...
消息队列有7组,我这只列出了3
NtUserGetMessage的执行流程:
User32!GetMessage() 调用 w32k!NtUserGetMessage()

co_IntGetPeekMessage()
co_IntPeekMessage()
co_MsqDispatchOneSentMessage()
co_IntCallSentMessageCallback()
KeUserModeCallback()//返回3环调用3环窗口过程

do
{
	//先判断SentMessagesListHead是否有消息 如果有处理掉
	do
	{
		....
		KeUserModeCallback(USER32_CALLBACK_WINDOWPROC,
                               Arguments,
                               ArgumentLength,
                               &ResultPointer,
                               &ResultLength);
		....
	}while(SentMessagesListHead != NULL)
	
	//依次判断其他的6个队列,里面如果有消息 返回  没有继续
}while(其他队列!=NULL)

详细版伪代码和上那个一样的

co_IntCallSentMessageCallback(SENDASYNCPROC CompletionCallback,
                              HWND hWnd,
                              UINT Msg,
                              ULONG_PTR CompletionCallbackContext,
                              LRESULT Result)
{......
	KeUserModeCallback(USER32_CALLBACK_SENDASYNCPROC,
                               &Arguments,
                               sizeof(SENDASYNCPROC_CALLBACK_ARGUMENTS),
                               &ResultPointer,
                               &ResultLength);......						   
}

co_MsqDispatchOneSentMessage(_In_ PTHREADINFO pti)
{......
	 co_IntCallSentMessageCallback(Message->CompletionCallback,
                                       Message->Msg.hwnd,
                                       Message->Msg.message,
                                       Message->CompletionCallbackContext,
                                       Message->lResult);......								   
}

co_IntPeekMessage(PMSG Msg,
                   PWND Window,
                   UINT MsgFilterMin,
                   UINT MsgFilterMax,
                   UINT RemoveMsg,
                   LONG_PTR *ExtraInfo,
                   BOOL bGMSG )
{......
	do
    {......
		 while ( co_MsqDispatchOneSentMessage(pti) )
        {
           if (HIWORD(RemoveMsg) && !bGMSG) Hit = TRUE;
        }......
	}while(SentMessagesListHead != NULL).....
}

co_IntGetPeekMessage(PMSG pMsg,
                      HWND hWnd,
                      UINT MsgFilterMin,
                      UINT MsgFilterMax,
                      UINT RemoveMsg,
                      BOOL bGMSG )
{......
	do
    {
        Present = co_IntPeekMessage( pMsg,
                                     Window,
                                     MsgFilterMin,
                                     MsgFilterMax,
                                     RemoveMsg,
                                     &ExtraInfo,
                                     bGMSG );......
		//依次判断其他的6个队列,里面如果有消息 返回  没有继续
	}while(其他队列!=NULL)......
}

SendMessage与PostMessage的区别(同步与异步):

1. 同步
SendMessage()发送消息,GetMessage();接收,进入0环要遍历SentMessagesListHead有没有消息,
有就处理,没有就返回,
必须要处理完才会返回结果,SendMessage()要接收到结果才会结束否则会一直 堵塞 在这

2. 异步
PostMessage()发送消息,GetMessage()只会接收它的消息,不会处理,
它的消息由TranslateMessage(&msg)DispatchMessage(&msg)来处理

PostMessage()发送完后是不会等待你的处理结果的,发完立马就结束

消息的分发

MSG msg;
while(GetMessage(&msg,NULL,0,0))
{
	TranslateMessage(&msg); //翻译消息 (处理键盘码的)
	DispatchMessage(&msg);  //分发消息 根据窗口句柄调用窗口过程
}

一个线程可以有多个窗口(按钮之类的控件也算窗口),它们共享着一个消息队列,
GetMessage()会把这个线程的所有消息都取出来,所以叫“分发消息”

TranslateMessage(&msg); //翻译消息

如果注释掉TranslateMessage(&msg)它就只能接收到键盘码
case WH_KEYDOWN:
{
	sprintf(szBuffer,"你按下了%d键",wParam);
	MessageBox(hwnd,szBuffer,NULL,MB_OK);
}

加了TranslateMessage(&msg)它就能帮你翻译为字符
case WH_CHAR:
{
	sprintf(szBuffer,"你按下了%c键",wParam);
	MessageBox(hwnd,szBuffer,NULL,MB_OK);
}

其他队列的处理流程:

1. User32!DispatchMessage() 调用 w32k!NtUserDispatchMessage()
2. IntDispatchMessage()
3. co_IntCallWindowProc()
4. KeUserModeCallback()  //返回3环调用3环窗口过程

<1> 根据窗口句柄找到窗口对象
<2> 根据窗口对象得到窗口过程函数,由0环发起调用

在回调函数里我们只处理我们需要的消息,别的消息给默认的窗口过程处理函数来处理

//默认窗口过程处理函数
return  DefWindowProc(hWnd, uMsg, wParam, lParam);
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值