工作管理員的「未回應」標籤是如何判定的?核心原理極其簡單:向視窗發送 WM_NULL 訊息,若超時未處理即視為停止回應。本文完整實作 SendMessageTimeout 方案,並補充未公開的 IsHungAppWindow/IsHungThread 進階用法。
SendMessageTimeout + WM_NULL
#include
BOOL IsWindowResponding(HWND hWnd, DWORD timeoutMs = 5000) {
// WM_NULL (0x0000) 什麼都不做,僅測試訊息佇列是否處理
DWORD_PTR result;
LONG_PTR ret = SendMessageTimeout(
hWnd, // 目標視窗
WM_NULL, // 訊息類型
0, 0, // wParam, lParam
SMTO_ABORTIFHUNG | SMTO_BLOCK, // 旗標:遇掛起中止、同步等待
timeoutMs, // 超時毫秒
&result // 回傳值(WM_NULL 無意義)
);
return ret != 0; // 非 0 表示視窗程序在超時前處理了訊息
}
| 旗標 | 效果 |
|---|---|
SMTO_ABORTIFHUNG | 若目標視窗屬於掛起執行緒,立即返回 0,不等待超時。 |
SMTO_BLOCK | 阻塞呼叫執行緒直到超時或處理完成(預設行為)。 |
SMTO_NORMAL | 若目標掛起則繼續等待(不中止)。 |
SMTO_NOTIMEOUTIFNOTHUNG | 目標未掛起時無視超時,持續等待。 |
位於 user32.dll,無標頭檔/匯入庫,需 GetProcAddress 動態載入。
IsHungAppWindow (NT 系列)
typedef BOOL (WINAPI *PFN_IsHungAppWindow)(HWND);
PFN_IsHungAppWindow pfnIsHungAppWindow =
(PFN_IsHungAppWindow)GetProcAddress(GetModuleHandle("user32.dll"), "IsHungAppWindow");
if (pfnIsHungAppWindow && pfnIsHungAppWindow(hWnd)) {
// 視窗屬於停止回應的應用程式
}
IsHungThread (9x 系列)
typedef BOOL (WINAPI *PFN_IsHungThread)(DWORD);
PFN_IsHungThread pfnIsHungThread =
(PFN_IsHungThread)GetProcAddress(GetModuleHandle("user32.dll"), "IsHungThread");
DWORD tid = GetWindowThreadProcessId(hWnd, NULL);
if (pfnIsHungThread && pfnIsHungThread(tid)) {
// 視窗執行緒停止回應
}
BOOL CALLBACK EnumProc(HWND hWnd, LPARAM lParam) {
if (!IsWindowVisible(hWnd)) return TRUE;
char title[256];
GetWindowText(hWnd, title, 256);
if (title[0] == 0) return TRUE;
BOOL responding = IsWindowResponding(hWnd, 2000);
printf("%s: %s\n", title, responding ? "正常" : "未回應");
return TRUE;
}
int main() {
EnumWindows(EnumProc, 0);
return 0;
}
PeekMessage 會被誤判;解決方案:長運算拆到背景執行緒或定期 MsgWaitForMultipleObjects。ChangeWindowMessageFilterEx 允許 WM_NULL。SendMessageTimeout(WM_NULL, SMTO_ABORTIFHUNG, 5000),標準、有文件、跨版本穩定。IsHungAppWindow,並做好動態載入失敗的降級處理。