Windows消息机制学习笔记(三)—— 消息的接收与分发

要点回顾

1)一个GUI线程包含一个消息队列

普通线程
↓
GUI线程
↓
THEWAD.W32THREAD
↓
THREADINFO
↓
消息队列

2)一个线程可以包含多个窗口,所有窗口共享一个消息队列

_WINDOW_OBJECT		//0环创建
↓
PTHREADINFO pti		//所属线程
↓
WNDPROC lpfnWndProc	//窗口过程(窗口回调函数)

消息循环

MSG msg;
while(GetMessage(&msg, NULL, 0, 0))	//从消息队列中取出消息
{
	TranslateMessage(&msg);	//加工消息
	DispatchMessage(&msg);	//分发消息
}

思考:使用SendMessage与PostMessage发送的消息位于同一组队列中吗?
答案:不同类型的消息被置于不同队列。

消息队列

描述:消息队列共有七组,用于存放不同类型的消息。
例如

1)SentMessagesListHead		//接收SendMessage发来的消息
2)PostedMessagesListHead	//接收PostMessage发来的消息
3)HardwareMessagesListHead	//接收键盘、鼠标等硬件设备的消息
...

完整队列

//ReactOS v3.12
typedef struct _USER_MESSAGE_QUEUE
{
	...
	/* Owner of the message queue */
	struct _ETHREAD *Thread;
	/* Queue of messages sent to the queue. */
	LIST_ENTRY SentMessagesListHead;
	/* Queue of messages posted to the queue. */
	LIST_ENTRY PostedMessagesListHead;
	/* Queue of sent-message notifies for the queue. */
	LIST_ENTRY NotifyMessagesListHead;
	/* Queue for hardware messages for the queue. */
	LIST_ENTRY HardwareMessagesListHead;
	...
	/* messages that are currently dispatched by other threads */
	LIST_ENTRY DispatchingMessagesHead;
	/* messages that are currently dispatched by this message queue, required for cleanup */
	LIST_ENTRY LocalDispatchingMessagesHead;
	...
}

消息的接收

GetMessage

描述:从消息队列中取出消息

BOOL WINAPI GetMessage(
	LPMSG lpMsg,	//返回从队列中取出的消息
	HWND hWnd,		//过滤条件一:窗口句柄
	UINT wMsgFilterMin,	//过滤条件二:最小值
	UINT wMsgFilterMax	//过滤条件三:最大值
);

主要功能:循环判断是否存在属于该窗口的消息,若存在,则将消息存储到MSG指定的结构中,并将消息从列表中删除。

注意:事实上,GetMessage还做了一件很重要的事情,即在接收消息时,将SentMessagesListHead中的消息进行处理

大致流程

User32!GetMessage
↓
w32k!NtUsrGetMessage
↓
do
{
	//先判断SentMessagesListHead中是否存在消息,若存在则进行处理
	do
	{
		...
		KeUserModeCallback(USER32_CALLBACK_WINDOWPROC,
			Arguments,
			ArgumentLength,
			&ResultPinter,
			&ResultLength);
		...
	}
}while(SentMessagesListHead != NULL)

源码(ReactOS v3.12)

BOOL APIENTRY
NtUserGetMessage( PNTUSERGETMESSAGEINFO UnsafeInfo,
                  HWND hWnd,
                  UINT MsgFilterMin,
                  UINT MsgFilterMax )
{
   ...

   do
   {
      GotMessage = co_IntPeekMessage(&Msg, Window, MsgFilterMin, MsgFilterMax, PM_REMOVE);
      ...
   }while (! GotMessage);	//对应外层循环

   ...
}
BOOL FASTCALL
co_IntPeekMessage( PUSER_MESSAGE Msg,
                   PWINDOW_OBJECT Window,
                   UINT MsgFilterMin,
                   UINT MsgFilterMax,
                   UINT RemoveMsg )
{
   ...

   /* Dispatch sent messages here. */
   while (co_MsqDispatchOneSentMessage(ThreadQueue));	//对应内层循环

   ...
}
BOOLEAN FASTCALL
co_MsqDispatchOneSentMessage(PUSER_MESSAGE_QUEUE MessageQueue)
{
   ...

   if (Message->HookMessage == MSQ_ISHOOK)
   {
      ...
   }
   else if (Message->HookMessage == MSQ_ISEVENT)
   {
      ...
   }
   else
   {
      /* 发送消息 */
      Result = co_IntSendMessage(Message->Msg.hwnd,
                                 Message->Msg.message,
                                 Message->Msg.wParam,
                                 Message->Msg.lParam);
   }
   
   /* 从消息队列中删除该消息 */
   RemoveEntryList(&Message->ListEntry);

   ...
   
   /* 调用回调函数 */
   if (Message->CompletionCallback != NULL)
   {
      co_IntCallSentMessageCallback(Message->CompletionCallback,
                                    Message->Msg.hwnd,
                                    Message->Msg.message,
                                    Message->CompletionCallbackContext,
                                    Result);
   }

   ...
}
VOID APIENTRY
co_IntCallSentMessageCallback(SENDASYNCPROC CompletionCallback,
                              HWND hWnd,
                              UINT Msg,
                              ULONG_PTR CompletionCallbackContext,
                              LRESULT Result)
{
   ...

   /* 回到三环进行处理 */
   Status = KeUserModeCallback(USER32_CALLBACK_SENDASYNCPROC,
                               &Arguments,
                               sizeof(SENDASYNCPROC_CALLBACK_ARGUMENTS),
                               &ResultPointer,
                               &ResultLength);

   ...
}

实验1:理解GetMessage

第一步:编译并运行程序A
#include <windows.h>

LRESULT CALLBACK WindowProc(
	IN HWND hwnd,
	IN UINT uMsg,
	IN WPARAM wParam,
	IN LPARAM lParam
){
	switch(uMsg)
	{
	case 0x401:
		MessageBoxA(NULL, "测试窗口接收到消息", "新消息", MB_OK);
		return false;
	}
	
	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}


int APIENTRY WinMain(
	HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPSTR lpCmdLine,
	int nShowCmd
){
	//窗口的类名
	TCHAR className[] = "My First Window";

	//创建一个自己的窗口
	WNDCLASS wndclass = {0};
	wndclass.hbrBackground = (HBRUSH)COLOR_MENU;
	wndclass.lpfnWndProc = WindowProc;
	wndclass.lpszClassName = className;
	wndclass.hInstance = hInstance;

	//注册
	RegisterClass(&wndclass);

	//创建窗口
	HWND hwnd = CreateWindow(
		className,
		TEXT("测试窗口"),
		WS_OVERLAPPEDWINDOW,
		10,
		10,
		600,
		300,
		NULL,
		NULL,
		hInstance,
		NULL);
	
	if(hwnd == NULL)
		return 0;
	
	//显示窗口
	ShowWindow(hwnd, SW_SHOW);
	
	//消息循环
	MSG msg;
	while(GetMessage(&msg, NULL, 0, 0))
	{
		//TranslateMessage(&msg);
		//DispatchMessage(&msg);
	}
	
	return 0;
}

运行结果
在这里插入图片描述
注意不要关闭窗口

第二步:编译并运行程序B
#include <stdio.h>
#include <windows.h>

int main()
{
	HWND hwnd = FindWindow("My First Window", "测试窗口");
	SendMessage(hwnd, 0x401, 0, 0);
	
	return 0;
}

运行结果
在这里插入图片描述

同步与异步

描述

  1. 使用SendMessage发送消息时,会等待消息处理之后再返回,这个过程称为同步
  2. 使用PostMessage发送消息时,不会等待消息处理,直接返回,这种方式称为异步

实验2:理解同步与异步

第一步:编译并运行实验1中的程序A

运行结果
在这里插入图片描述

第二步:编译并运行实验1中的程序B

在return处设置断点
在这里插入图片描述运行结果
在这里插入图片描述此时,程序B并未继续向下执行。

第三步:点击确定按钮

执行结果
在这里插入图片描述程序A处理消息后,程序B继续向下执行。

第四步:修改程序B的代码,再次运行

新代码

#include <stdio.h>
#include <windows.h>

int main()
{
	HWND hwnd = FindWindow("My First Window", "测试窗口");
	PostMessage(hwnd, 0x401, 0, 0);		//SendMessage改为PostMessage
	
	return 0;
}

执行结果
在这里插入图片描述程序B发送消息后未等待目标处理继续向下执行,程序A并未处理消息。

消息的分发

描述:GetMessage能够处理SentMessagesListHead队列中的消息,其他队列中的消息则由DispatchMessage进行分发处理。

大致流程

  1. 根据窗口句柄找到窗口对象
  2. 根据窗口对象得到窗口过程函数,由0环进行调用。

源码(ReactOS v3.12)

LRESULT APIENTRY
NtUserDispatchMessage(PMSG UnsafeMsgInfo)
{
  ...
  
  if (!Hit) Res = IntDispatchMessage(&SafeMsg);

  ...
}
LRESULT
FASTCALL
IntDispatchMessage(PMSG pMsg)
{
  ...
  
  retval = co_IntCallWindowProc( Window->Wnd->lpfnWndProc,
                                !Window->Wnd->Unicode,
                                 pMsg->hwnd,
                                 pMsg->message,
                                 pMsg->wParam,
                                 lParamPacked,
                                 lParamBufferSize);

  ...
}
LRESULT APIENTRY
co_IntCallWindowProc(WNDPROC Proc,
                     BOOLEAN IsAnsiProc,
                     HWND Wnd,
                     UINT Message,
                     WPARAM wParam,
                     LPARAM lParam,
                     INT lParamBufferSize)
{
   ...

   /* 回到三环,调用窗口过程函数 */
   Status = KeUserModeCallback(USER32_CALLBACK_WINDOWPROC,
                               Arguments,
                               ArgumentLength,
                               &ResultPointer,
                               &ResultLength);

   ...
}

DispatchMessage

描述:用于消息分发,根据窗口句柄调用相关的窗口过程,通常用于分发由GetMessage函数检索到的消息。

LRESULT DispatchMessage(
  CONST MSG *lpmsg   // message information
);

思考:为什么DispatchMessage要对消息进行分发而不统一处理?
答案:所有窗口共享一个消息队列,且每个窗口都拥有属于自己的回调函数。

MSG结构体成员

typedef struct tagMSG {
  HWND   hwnd;		//窗口句柄
  UINT   message; 
  WPARAM wParam; 
  LPARAM lParam; 
  DWORD  time; 
  POINT  pt; 
} MSG, *PMSG; 

DispatchMessage根据MSG->hwnd调用各句柄对应窗口对象的回调函数。

思考:既然GetMessage已经得到了句柄信息,为什么不能直接调用对应的回调函数而需要分发。
答案:因为句柄指向的是句柄表,而不是窗口对象,需要进入0环通过句柄表找到窗口对象,才能调用对应的回调函数。

实验3:理解DispatchMessage

第一步:编译并运行以下代码
#include <stdio.h>
#include <windows.h>

LRESULT CALLBACK WindowProc(
	IN HWND hwnd,
	IN UINT uMsg,
	IN WPARAM wParam,
	IN LPARAM lParam
){

	switch(uMsg)
	{
	case 0x401:
		{
			MessageBoxA(NULL, "测试窗口接收到消息", "新消息", MB_OK);
			return 0;
		}
	case WM_DESTROY:
		{
			ExitProcess(0);
			return 0;
		}
	}
	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}


int APIENTRY WinMain(
	HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPSTR lpCmdLine,
	int nShowCmd
){
	//窗口的类名
	TCHAR className[] = "My First Window";

	//创建一个自己的窗口
	WNDCLASS wndclass = {0};
	wndclass.hbrBackground = (HBRUSH)COLOR_MENU;
	wndclass.lpfnWndProc = WindowProc;
	wndclass.lpszClassName = className;
	wndclass.hInstance = hInstance;

	//注册
	RegisterClass(&wndclass);

	//创建窗口
	HWND hwnd = CreateWindow(
		className,
		TEXT("测试窗口"),
		WS_OVERLAPPEDWINDOW,
		10,
		10,
		600,
		300,
		NULL,
		NULL,
		hInstance,
		NULL);
	
	if(hwnd == NULL)
		return 0;
	
	//显示窗口
	ShowWindow(hwnd, SW_SHOW);
	
	//消息循环
	MSG msg;
	while(GetMessage(&msg, NULL, 0, 0))
	{
		//TranslateMessage(&msg);
		//DispatchMessage(&msg);
	}
	
	return 0;
}

运行结果
在这里插入图片描述

第二步:操作窗口

拖动窗口、点击最小化、缩放、关闭按钮均无反应

第三步:去掉DispatchMessage注释,重新运行
MSG msg;
while(GetMessage(&msg, NULL, 0, 0))
{
	//TranslateMessage(&msg);
	DispatchMessage(&msg);
}
第四步:再次操作窗口

拖动窗口、点击最小化、缩放、关闭按钮均得到相应

TranslateMessage

描述:用于将虚拟键码转换为字符消息。该字符消息又被发送给对应线程(调用TranslateMessage的线程)的消息队列,当线程再次调用GetMessage函数或PeekMessage函数获取消息的时候被读取。

实验4:理解TranslateMessage

第一步:编译并运行以下代码
#include <stdio.h>
#include <windows.h>

LRESULT CALLBACK WindowProc(
	IN HWND hwnd,
	IN UINT uMsg,
	IN WPARAM wParam,
	IN LPARAM lParam
){
	TCHAR szBuffer[MAX_PATH];

	switch(uMsg)
	{
	/*
	case WM_KEYDOWN:
		{
			sprintf(szBuffer, "键%d被你按下了\x00", wParam);
			MessageBox(NULL, szBuffer, "WM_KEYDOWN", MB_OK);
			return 0;
		}
	*/
	case WM_CHAR:
		{
			sprintf(szBuffer, "你按下了%c键\x00", wParam);
			MessageBox(NULL, szBuffer, "WM_CHAR", MB_OK);
			return 0;
		}
	
	}
	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}


int APIENTRY WinMain(
	HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPSTR lpCmdLine,
	int nShowCmd
){
	//窗口的类名
	TCHAR className[] = "My First Window";

	//创建一个自己的窗口
	WNDCLASS wndclass = {0};
	wndclass.hbrBackground = (HBRUSH)COLOR_MENU;
	wndclass.lpfnWndProc = WindowProc;
	wndclass.lpszClassName = className;
	wndclass.hInstance = hInstance;

	//注册
	RegisterClass(&wndclass);

	//创建窗口
	HWND hwnd = CreateWindow(
		className,
		TEXT("测试窗口"),
		WS_OVERLAPPEDWINDOW,
		10,
		10,
		600,
		300,
		NULL,
		NULL,
		hInstance,
		NULL);
	
	if(hwnd == NULL)
		return 0;
	
	//显示窗口
	ShowWindow(hwnd, SW_SHOW);
	
	//消息循环
	MSG msg;
	while(GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	
	return 0;
}

运行结果
在这里插入图片描述

第二步:敲击不同按键

1)小写模式,按下"A":
在这里插入图片描述
2)按下大小写键,切换到大写模式:
无弹窗

3)大写模式,按下”A“:
在这里插入图片描述
4)按下shift键:
无弹窗

5)小写模式,按下shift+“A”:
在这里插入图片描述

第三步:修改代码并重新运行程序

新代码

#include <stdio.h>
#include <windows.h>

LRESULT CALLBACK WindowProc(
	IN HWND hwnd,
	IN UINT uMsg,
	IN WPARAM wParam,
	IN LPARAM lParam
){
	TCHAR szBuffer[MAX_PATH];

	switch(uMsg)
	{
	case WM_KEYDOWN:
		{
			sprintf(szBuffer, "键%d被你按下了\x00", wParam);
			MessageBox(NULL, szBuffer, "WM_KEYDOWN", MB_OK);
			return 0;
		}
	/*
	case WM_CHAR:
		{
			sprintf(szBuffer, "你按下了%c键\x00", wParam);
			MessageBox(NULL, szBuffer, "WM_CHAR", MB_OK);
			return 0;
		}
	*/
	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}


int APIENTRY WinMain(
	HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPSTR lpCmdLine,
	int nShowCmd
){
	//窗口的类名
	TCHAR className[] = "My First Window";

	//创建一个自己的窗口
	WNDCLASS wndclass = {0};
	wndclass.hbrBackground = (HBRUSH)COLOR_MENU;
	wndclass.lpfnWndProc = WindowProc;
	wndclass.lpszClassName = className;
	wndclass.hInstance = hInstance;

	//注册
	RegisterClass(&wndclass);

	//创建窗口
	HWND hwnd = CreateWindow(
		className,
		TEXT("测试窗口"),
		WS_OVERLAPPEDWINDOW,
		10,
		10,
		600,
		300,
		NULL,
		NULL,
		hInstance,
		NULL);
	
	if(hwnd == NULL)
		return 0;
	
	//显示窗口
	ShowWindow(hwnd, SW_SHOW);
	
	//消息循环
	MSG msg;
	while(GetMessage(&msg, NULL, 0, 0))
	{
		//TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	
	return 0;
}

运行结果
在这里插入图片描述

第四步:敲击不同按键

1)小写模式,按下"A":
在这里插入图片描述2)按下大小写键,切换到大写模式:
在这里插入图片描述
3)大写模式,按下”A“:
在这里插入图片描述
4)按下shift键:
在这里插入图片描述

总结

  1. GetMessage的不止能够取出消息,还能够处理消息
  2. GetMessage处理消息时,从0环回到3环调用窗口过程函数进行处理。
  3. SendMessage发送消息后会等待目标处理完毕,PostMessage不会。
  4. GetMessage只会处理SentMessagesListHead列表中的消息。
  5. DispatchMessage的作用是分发除了SentMessagesListHead队列以外的消息给各窗口对象进行处理。
  6. TranslateMessage只处理键盘消息,将虚拟键码转换为字符消息,再由DispatchMessage进行分发。
  7. 未经过TranslateMessage处理的键盘消息,虚拟键码由WM_KEYDOWN进行处理;经过TranslateMessage处理的键盘消息,字符消息由WM_CHAR进行处理
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值