Win32判断指定进程是否无响应(枚举窗口法)

在任务管理器和资源监视器中,我们能看到的进程状态除了已挂起,还有无响应。想想全屏游戏时,突然游戏无响应了,我们说这叫死机了,都只能心有不甘地重启电脑(当然是可以用快捷键启动任务管理器的)。

未响应

在Windows系统中,一个程序无响应的定义是: “如果应用程序未等待输入、未在启动处理中且未在5秒的内部超时期限内调用PeekMessage(),则应用程序被视为未响应。”显然,这里的条件只适用于GUI程序,像没有消息循环的控制台程序也就无所谓无响应的概念了。

那么,如何判断某个进程是否属于无响应状态呢?我之前写过一篇博文win32 判断进程状态(挂起/运行中)、用API挂起/恢复进程,讲了判断进程状态的办法,提了一嘴无响应不算挂起。
Windows没有提供API给我们直接判断一个进程是否无响应,但既然在任务管理器可以看到这个状态,那么我们也就可以参考任务管理器的思路进行分析。

分析任务管理器

Windows 8以上版本的任务管理器是通过枚举所有窗口来进行判断的。

经过IDA分析,任务管理器进程监视的核心函数WdcProcessMonitor::Update()检查并调用_CreateHangDetectionThread()创建了专门用于监测进程状态的线程,线程函数HangDetectionThread()又定期调用UpdateHangStatus(),该函数调用了APIEnumDesktops()枚举全部桌面,对于每个桌面,又调用了APIEnumDesktopWindows()函数对窗口逐个判断。

判断窗口是否无响应时,调用了APIIsHungAppWindow(),这是最中心的函数。尽管文档已经说明该函数不用于常规用途,之后可能更改或弃用,但是Windows 11最新的任务管理器仍在使用它,所以大可无视这个警告。

对于每个窗口,任务管理器的判断流程如下。

EnumWindowsProc
这里面用到了两个未公开函数,涉及了一个概念:幽灵窗口。

幽灵窗口

User32.dll
	HWND WINAPI GhostWindowFromHungWindow(HWND hwndHung);
	HWND WINAPI HungWindowFromGhostWindow(HWND hwndGhost);

我们常看到这样的现象,程序无响应时,窗口在点击时蒙上一层白色,此时窗口依然能够被拖动位置。按理说无响应窗口不会再处理消息,那么这个窗口是怎么回事?

事实上,在窗口无响应时,Windows桌面窗口管理器(dwm.exe)会在其上覆盖一个假窗口,以便用户移动或者最小化它。这种窗口叫做幽灵窗口(Ghost Window,官方译名为“重影窗口”),窗口类名为Ghost,它复制了本体窗口的客户区内容以图片形式显示,本体窗口则被隐藏了。我们能够拖动的正是幽灵窗口。

有的情景需求是调用APIGetWindowFromPoint()想要获取到对应位置的窗口句柄,但是对于无响应窗口,获取到的是它的幽灵窗口,想要正确找到目标,就需要HungWindowFromGhostWindow()获取本体窗口,而另一个函数GhostWindowFromHungWindow()功能相反,是根据本体窗口获取幽灵窗口的句柄。

不想要系统在窗口无响应时生成幽灵窗口,可以调用APIDisableProcessWindowsGhosting(),它能够禁止当前进程窗口的“重影”特性。

判断进程是否无响应

参考任务管理器的逻辑,可以写出判断进程是否无响应的代码实现。要注意的是,这里的实例是极简型的,只判断单一进程(所以不涉及幽灵窗口),并且在目标进程的一个窗口无响应时,就认为整个进程无响应。如果需要更加扩展的功能,就可以参照上面所述的办法枚举所有桌面,再枚举桌面上的所有窗口,用任务管理器的完整思路来进行判断。

#include<Windows.h>

struct WNDINFO{
	DWORD pid;
	bool bNotResponding;
}; //定义用来传输数据的信息结构
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) {
	WNDINFO* info = (MW_INFO*)lParam; DWORD pid;
	//过滤不属于目标进程的窗口
	GetWindowThreadProcessId(hwnd, &pid);
	if(pid != info->pid)return TRUE;
	//判断流程
	info->bNotResponding = false;
	HWND hOwner = GetWindow(hwnd, GW_OWNER);
	LONG l = GetWindowLong(hwnd, GWL_EXSTYLE);
	if((!hOwner || !IsWindowVisible(hOwner) || (l & WS_EX_APPWINDOW))
	   && (l & WS_EX_TOOLWINDOW) == 0 && IsHungAppWindow(hwnd)){
		info->bNotResponding = true;
		return FALSE;
	}
	return TRUE;
}

//用该函数判断进程是否无响应
BOOL IsProcessNotResponding(DWORD pid){
	//这个结构作为信息交换的载体
	WNDINFO info = {}; info.pid = id;
	EnumWindows(EnumWindowsProc, LPARAM(&info));
	return info.bNotResponding;
}

参考资料

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值