Windows消息机制学习笔记(四)—— 内核回调机制

要点回顾

1)GetMessage不仅能够取出消息,还能处理SentMessagesListHead队列中的消息
2)DispatchMessage用于处理其它队列中的消息。

内核调用

描述:窗口过程函数除了会在消息循环中被调用,一些0环的代码也可以直接发起调用。

例如:窗口初始化时、窗口创建时、窗口显示时。

目的:使窗口在被初始化、被创建、被显示时,用户能够有机会做一些事情。

实验1:理解内核调用

第一步:编译并运行以下代码

注意:运行前在while(GetMessage(&msg, NULL, 0, 0))这行设置断点。

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

#define DPRINTF_BUF_SZ 1024

void OutputDebugStringf(char *fmt, ...)
{
#ifdef _DEBUG
	va_list args;
	char buf[DPRINTF_BUF_SZ];
	
	va_start(args, fmt);
	vsprintf(buf, fmt, args);
	OutputDebugString(buf);
#endif
}

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

	switch(uMsg)
	{
	case WM_DESTROY:
		{
			ExitProcess(0);
			return 0;
		}
	}

	OutputDebugStringf("消息类型:%x \n", uMsg);

	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;
}

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

第二步:修改窗口过程函数,重新运行

注意:仍然在while(GetMessage(&msg, NULL, 0, 0))这行设置断点。

新窗口过程函数

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

	switch(uMsg)
	{
	case WM_CREATE:
		{
			MessageBoxA(NULL, "窗口被创建了", "CREATE", MB_OK);
			return 0;
		}
	case WM_DESTROY:
		{
			ExitProcess(0);
			return 0;
		}
	}

	OutputDebugStringf("消息类型:%x \n", uMsg);

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

运行结果
在这里插入图片描述
不难发现,程序并未进入消息循环,却调用了窗口过程函数,输出了消息与弹窗,说明消息不是只有当调用GetMessage与DispatchMessage时才处理。

KeUserModeCallback

描述
1)从0环调用3环函数的几种方式

  • APC
  • 异常
  • 内核回调

APC异常回三环的落脚点比较单一,而消息机制需要处理的情况较多,不能使用同一个逻辑进行处理,因此消息机制使用内核回调调用三环函数。

2)回到3环的落脚点

  • APCntdll!KiUserApcDispatcher
  • 异常ntdll!KiUserExceptionDispatcher

3)内核回调在3环的落脚点KeUserModeCallback

PEB+0x2C指向回调函数地址表。

4)凡是有窗口的程序就有可能0环直接调用3环的程序。


声明

NTSTATUS
NTAPI
KeUserModeCallback(
    IN ULONG FunctionID,	//索引,指向三环落脚点
    IN PVOID InputBuffer,	//包含窗口回调函数与相关重要信息
    IN ULONG InputLength,
    OUT PVOID *OutputBuffer,
    OUT PULONG OutputLength
);

FunctionID索引列表

#define USER32_CALLBACK_WINDOWPROC            (0)
#define USER32_CALLBACK_SENDASYNCPROC         (1)
#define USER32_CALLBACK_LOADSYSMENUTEMPLATE   (2)
#define USER32_CALLBACK_LOADDEFAULTCURSORS    (3)
#define USER32_CALLBACK_HOOKPROC              (4)
#define USER32_CALLBACK_EVENTPROC             (5)
#define USER32_CALLBACK_LOADMENU              (6)
#define USER32_CALLBACK_CLIENTTHREADSTARTUP   (7)
#define USER32_CALLBACK_MAXIMUM               (7)

实验2:在OD中查看回调函数地址表

第一步:加载程序

在这里插入图片描述

第二步:在内存中查看TEB

在这里插入图片描述
在这里插入图片描述

第三步:查看PEB数据

PEB位于TIB+0x30位置
在这里插入图片描述在这里插入图片描述

第四步:查看回调地址表

回调地址表位于PEB+0x2C位置
在这里插入图片描述在这里插入图片描述

总结

  1. 不是所有消息都是在进入消息循环后由GetMessageDispatchMessage进行处理的,部分内核代码也能够调用窗口处理函数。
  2. 内核代码通过KeUserModeCallback调用窗口处理函数,由第一个参数FunctionID决定回到三环时的落脚点。
  3. FunctionID的值为回调地址表的索引,回调地址表位于PEB+0x2C位置,每个值为一个函数地址。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值