Иногда для работы требуется определение зависших приложений, окна которых не отвечают на сообщения. Для этого есть два способа. Первый - официально документированный, через функцию
SendMessageTimeOut. Особенность ее работы заключается в том, что после отправки сообщения окну она ждет ответ заданное время, и, если ответа от приложения не последовало, то возвращает FALSE. Вот пример использования функции. Нужные константы, как обычно, в FASM не определены, пришлось брать их из других источников.
...
; Определить таймаут 50 миллисекунд
TIMEOUT = 50
; Определить константу SMTO_ABORTIFHUNG
SMTO_ABORTIFHUNG = 2
; hwnd - хэндл проверяемого окна
invoke SendMessageTimeout,[hwnd],NULL,0,0,SMTO_ABORTIFHUNG,TIMEOUT,NULL
; Если вернулся 0, то приложение "висит"
or eax,eax
jz app_hung_up
...
Минус использования этой функции в том, что при частой проверке большого количества окон зависших приложений, каждый раз будет отрабатываться таймаут для каждого такого окна, что суммарно может дать снижение производительности вашего приложения. Но повторюсь, Microsoft официально рекомендует к использованию именно эту функцию.
Второй способ - недокументированный. В Windows есть особые функции, которые используются, например, во встроенном Диспетчере задач. Для операционных систем Windows 2000 и выше это
IsHungAppWindow, а для систем Windows 9x и Windows ME - функция IsHungThread (она вообще никак не документирована на MSDN). Разница между ними в том, что IsHungAppWindow работает с хэндлом окна, а IsHungThread с хэндлом потока окна. Поскольку в системной библиотеке user32.dll одновменно может быть только одна из этих функций, то включать их в секцию импорта нельзя, иначе ваше приложение вылетит с ошибкой. Также есть информация, что они вообще недоступны через экспорт. Стало быть воспользуемся штатными функциями API
GetModuleHandle и
GetProcAddress, но предварительно в секции данных определим нужные нам переменные:
section '.data' data readable writeable
...
hUser dd ? ; Хэндл библиотеки user32.dll
hIHW dd ? ; Адрес функции IsHungAppWindow (Win2k и выше)
hIHT dd ? ; Адрес функции IsHungThread (Win9x)
strUser db 'user32.dll',0
strIHW db 'IsHungAppWindow',0
strIHT db 'IsHungThread',0
...
Перед использованием надо получить все необходимые адреса функций:
Code (Assembler) : Убрать нумерацию
section '.code' code readable executable
...
mov [hIHW],0 ; Инициализировать переменные
mov [hIHT],0
; Получить хэндл библиотеки user32.dll
invoke GetModuleHandle,strUser
mov [hUser],eax
chk_WinNT:
; Попытаться получить адрес функции IsHungAppWindow
invoke GetProcAddress,[hUser],strIHW
or eax,eax
jz chk_Win9X
mov [hIHW],eax ; Сохранить адрес функции
jmp @f
chk_Win9X:
; Попытаться получить адрес функции IsHungThread
invoke GetProcAddress,[hUser],strIHT
or eax,eax
jz loc_fatal_error
mov [hIHT],eax ; Сохранить адрес функции
jmp @f
loc_fatal_error:
; По какой-то немыслимой причине не найдено ни одной функции
...
@@:
; Продолжить обработку
...
И теперь сама функция проверки окна приложения на предмет его зависания. Для большей универсальности я дополнил ее кодом первого документированного способа, он будет вызываться в случае, когда не определена ни одна из функций второго способа. Если время выполнения функции имеет критическое значение, например, при частом обновлении большого списка процессов или окон, то предпочтительнее воспользоваться именно этим ее вариантом.
;----------------------------------------------------------------
; Процедура проверки окна на зависание его родительского приложения
; Предварительно должны быть определены переменные hIHW или hIHT
; с адресами функций IsHungAppWindow или IsHungThread
;
; На входе:
; hwnd - хэндл проверяемого окна
; На выходе:
; EAX = 0 - приложение работает или такое окно не найдено
; EAX = 1 - приложение зависло и не отвечает
;----------------------------------------------------------------
proc IsAppHung hwnd:dword
local tmp:DWORD ; Временная переменная
pusha
; Такое окно есть?
invoke IsWindow,[hwnd]
or eax,eax
jz .loc_ret
.chk_WinNT:
; Адрес функции IsHungAppWindow известен?
cmp [hIHW],0
je .chk_Win9x
stdcall [hIHW],[hwnd]
jmp .loc_ret
.chk_Win9x:
; Адрес функции IsHungThread известен?
cmp [hIHT],0
je .chk_All
invoke GetWindowThreadProcessId,[hwnd],NULL
stdcall [hIHT],eax
jmp .loc_ret
.chk_All:
; Определить таймаут 50 миллисекунд
TIMEOUT = 50
; Определить константу SMTO_ABORTIFHUNG
SMTO_ABORTIFHUNG = 2
invoke SendMessageTimeout,[hwnd],NULL,0,0,SMTO_ABORTIFHUNG,TIMEOUT,NULL
sub eax,1
neg eax
.loc_ret:
; Сохранить значение
mov [tmp],eax
; Восстановить регистры и сохраненное значение
popa
mov eax,[tmp]
ret
endp
Если очень частый вызов проверки не требуется, то код инициализации можно включить в саму функцию IsAppHung. В результате получится универсальная и самодостаточная функция, не требующая для работы вообще никаких сторонних переменных.
;----------------------------------------------------------------
; Процедура проверки окна на зависание его родительского приложения
;
; На входе:
; hwnd - хэндл проверяемого окна
; На выходе:
; EAX = 0 - приложение работает или такое окно не найдено
; EAX = 1 - приложение зависло и не отвечает
;----------------------------------------------------------------
proc IsAppHung hwnd:dword
local tmp:DWORD ; Временная переменная
pusha
; Такое окно есть?
invoke IsWindow,[hwnd]
or eax,eax
jz .loc_ret
; Получить хэндл библиотеки user32.dll
invoke GetModuleHandle,strUser
mov [tmp],eax
.chk_WinNT:
invoke GetProcAddress,[tmp],strIHW
or eax,eax
jz .chk_Win9x
stdcall eax,[hwnd]
jmp .loc_ret
.chk_Win9x:
invoke GetProcAddress,[tmp],strIHT
or eax,eax
jz .chk_All
mov [tmp],eax
invoke GetWindowThreadProcessId,[hwnd],NULL
stdcall [tmp],eax
jmp .loc_ret
.chk_All:
; Определить таймаут 50 миллисекунд
TIMEOUT = 50
; Определить константу SMTO_ABORTIFHUNG
SMTO_ABORTIFHUNG = 2
invoke SendMessageTimeout,[hwnd],NULL,0,0,SMTO_ABORTIFHUNG,TIMEOUT,NULL
sub eax,1
neg eax
.loc_ret:
; Сохранить значение
mov [tmp],eax
; Восстановить регистры и сохраненное значение
popa
mov eax,[tmp]
ret
strUser db 'user32.dll',0
strIHW db 'IsHungAppWindow',0
strIHT db 'IsHungThread',0
endp
По результатам полевых испытаний функции выявлены некоторые интересные факты. В частности выполнялся полный перебор всех top-level окон с проверкой их на предмет зависания. В процессе перебора на рабочей системе Windows XP были пойманы два системных окна с неизменными заголовками "M" и "Default IME", которые определялись как зависшие. Однако это не так, просто эти окна принадлежат к критическим системным процессам. Поэтому если ваша программа также выполняет проверку всех окон, то эти два окна в обработчике зависших процессов надо будет просто пропускать после проверки их заголовка.
Для имитации зависшего приложения можно воспользоваться программой
Bad Application или программой hangup.exe из архива. В архиве примеры программ для обнаружения зависших приложений всеми тремя способами и программа-имитатор зависшего приложения. Имитаторы зависших приложений придется снимать только через менеджер процессов.