Добро пожаловать в форум, Guest  >>   Войти | Регистрация | Поиск | Правила | В избранное | Подписаться
Все форумы / Delphi Новый топик    Ответить
 WinAPI: как по ProcessID получить дексрикторы окон программы?  [new]
InterSky
Member

Откуда:
Сообщений: 575
Задача: следить, не зависла ли почтовая программа. Если зависла - прибить и перезапустить.
Всё работает, но нашлась проблема: если за время пока эта программа была зависшей пришло сообщение - то программа при запуске выводит сообщение (нестандартное) о том что пришли новые письма и предлагает их показать. Пока не подтвердишь/откажешься от этого предложения посмотреть письма - не появляется окно на ТаскБаре (хотя само главное окно открыто). В результате, пока не откажусь, не могу найти дикриптор главного окна по FindWindow(nil, 'octoMail');

P.S. Ещё интересней с попыткой определить у убить программу... Это уже не вопрос. Это просто может кому-то на заметку. Определить что программа зависла я могу по имени запускного файла. Когда программа зависает - система говорит что её запускной файл explorer.exe :)
То есть через FindWindow(nil, 'octoMail'); я отыскиваю окно программы, потом через GetWindowThreadProcessId определяю идентификатор процесса программы, и потом проверю GetExeNameByProcID - если это octomail.exe значит программа жива, а если explorer.exe то она зависла. Но самое смешное - "КАК УБИТЬ ЗАВИСШУЮ ПРОГРАММУ?" Казалось бы всё просто - убиваем процесс через TerminateProcess( OpenProcess(PROCESS_TERMINATE,False,ProcID), 0); и всё. А вот теперь угадайте - какой процесс выдаёт нам виндоус на зависшую программу? Подсказка уже была выше - он выдаёт Explorer! И если я убиваю этот процесс - то грохается и автоматически рестартуется именно рабочий стол винды (первый запуск explorer всегда превращается в Рабочий стол, а второй и последующие уже выводят Проводник). Но если подать команду на убитие процесса дважды, то первым ударом убивается Рабочий стол (explorer), а уже вторым (в течении той секунды пока перезапускается Рабочий стол) можно убить зависшую программу. Если же рабочий стол уже запустился (ну скажем между двумя TerminateProcess была пауза больше секунды) то на запрос GetExeNameByProcID мы опять будем получать explorer.exe
В общем - чудеса, приходится приспосабливаться...
2 июл 19, 13:01    [21918934]     Ответить | Цитировать Сообщить модератору
 Re: WinAPI: как по ProcessID получить дексрикторы окон программы?  [new]
Dimitry Sibiryakov
Member

Откуда:
Сообщений: 48425

InterSky
чудеса, приходится приспосабливаться...
Какие только чудеса проктостоматологической изворотливости демонстрируют
программисты только лишь бы не исправлять бага, приводящего к зависанию...

Posted via ActualForum NNTP Server 1.5

2 июл 19, 13:04    [21918938]     Ответить | Цитировать Сообщить модератору
 Re: WinAPI: как по ProcessID получить дексрикторы окон программы?  [new]
Мимопроходящий
Member

Откуда: бурятский тундрюк, эсквайр
Сообщений: 30502

в задачу твою не вникал.

решить можно так:
делаешь EnumWindows()
для каждого найденного окошка вызываешь GetWindowThreadProcessID()
сверяешь полученный PID с нужным тебе
ну а дальше, на твоё усмотрение

Posted via ActualForum NNTP Server 1.5

2 июл 19, 13:10    [21918945]     Ответить | Цитировать Сообщить модератору
 Re: WinAPI: как по ProcessID получить дексрикторы окон программы?  [new]
Leonid Kudryavtsev
Member

Откуда:
Сообщений: 7934
Dimitry Sibiryakov
InterSky
чудеса, приходится приспосабливаться...
Какие только чудеса проктостоматологической изворотливости демонстрируют
программисты только лишь бы не исправлять бага, приводящего к зависанию...


Автор какие-то страсти расказывает. Может у него вирус какой на компьютере и оконую ф-цию кто-то перехватывает?

Мне тяжко представить. что у "зависшей" программы, что-то где-то в окнах должно меняться. Нормальное "зависла" - это ушла в цикл и не выгребает сообщения из очереди сообщений. Но что бы при этом идентификатор thread'а (process'а) у окна менялся - это какие-то русские народные сказки. IMHO & AFAIK

Так же не понятно, что если нужно прибить процесс, зачем автор ищет окна. Нашли процесс, его и прибили.

https://docs.microsoft.com/en-us/windows/desktop/procthread/process-enumeration
2 июл 19, 14:38    [21919063]     Ответить | Цитировать Сообщить модератору
 Re: WinAPI: как по ProcessID получить дексрикторы окон программы?  [new]
Aniskin
Member

Откуда:
Сообщений: 320
InterSky
Ещё интересней с попыткой определить у убить программу... Это уже не вопрос. Это просто может кому-то на заметку. Определить что программа зависла я могу по имени запускного файла. Когда программа зависает - система говорит что её запускной файл explorer.exe :).


https://docs.microsoft.com/en-us/windows/desktop/win7appqual/preventing-hangs-in-windows-applications

If the thread does not service the queue by calling GetMessage(), messages are not processed, and the window hangs: it can neither redraw nor can it accept input from the user.
...
The Desktop Window Manager assists by seamlessly hiding and then replacing the hung window with a 'ghost' copy displaying a bitmap of the original window's previous client area (and adding "Not Responding" to the title bar). As long as the original window's thread does not retrieve messages, the DWM manages both windows simultaneously, but allows the user to interact only with the ghost copy. Using this ghost window, the user can only move, minimize, and - most importantly - close the unresponsive application, but not change its internal state.
2 июл 19, 14:50    [21919076]     Ответить | Цитировать Сообщить модератору
 Re: WinAPI: как по ProcessID получить дексрикторы окон программы?  [new]
Leonid Kudryavtsev
Member

Откуда:
Сообщений: 7934
Aniskin


Спасибо.
2 июл 19, 14:55    [21919081]     Ответить | Цитировать Сообщить модератору
 Re: WinAPI: как по ProcessID получить дексрикторы окон программы?  [new]
InterSky
Member

Откуда:
Сообщений: 575
Dimitry Sibiryakov
Какие только чудеса проктостоматологической изворотливости демонстрируют программисты только лишь бы не исправлять бага, приводящего к зависанию...

Я не разработчик почтовой программы. Моя задача контролировать её работоспособность (если зависла - перезапустить).



Мимопроходящий
в задачу твою не вникал.

решить можно так:
делаешь EnumWindows()
для каждого найденного окошка вызываешь GetWindowThreadProcessID()
сверяешь полученный PID с нужным тебе
ну а дальше, на твоё усмотрение

Это действительно вариант. Но надо ли перебирать все существующие окна?
Насколько я понимаю (возможно вы меня поправите), иерархия строится следующим образом: есть процесс, у процесса есть нити, у нитей есть окна, у окон уже свои потомки...
Может всё же есть возможность зная PID запросить только свои нити (скорей всего она будет одна) и там получить список окон?


Leonid Kudryavtsev
Автор какие-то страсти расказывает...

Как я понял, слова пользователя Aniskin убедили вас в моей правоте.
При этом я не говорил что у зависшей программы поменялось что-то в Окнах (хотя об этом сказал позже Aniskin), я говорил что у зависшей программы сменилось имя родительского .EXE-файла который я получаю через обращение к её Процессу (поменялось в её процессе, а не в окне). Возможно даже в случае зависания сам GetWindowThreadProcessId выдаёт изначально не её процесс! К сожалению (или счастью), но зависания не так часты, а восстановить надо немедленно, и поиграться перепробовав все идеи не успеваешь.


Leonid Kudryavtsev
Так же не понятно, что если нужно прибить процесс, зачем автор ищет окна. Нашли процесс, его и прибили.

Мне надо убить зависшую программу. Я нахожу окно программы, по нему нахожу процесс и убиваю.
Ну хорошо, я получаю список всех запущенных процессов и могу по нему пробежаться. Дальше что? Как я узнаю что это мой процесс?
Я нахожу свой процесс своей программы в две строки: FindWindow и GetWindowThreadProcessId.
Вы же предлагаете написать 17 строк чтобы перебрать все процессы через Process32Next, потом ещё как-то определить какой из них мой, и говорите что не понимаете меня почему я не делаю вашим образом?

В реальности же, ещё не зная как определить "Зависание", я начал искать по какому критерию могу это обнаружить и заметив через одну из сторонних программ что зависшая программа выдаёт неправильное имя в GetExeNameByProcID, первым делом проверил список процессов:
procedure TForm1.Button1Click(Sender: TObject);
var
  hSnapShot: THandle;
  ProcInfo: TProcessEntry32;
begin
  hSnapShot := CreateToolHelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  if (hSnapShot <> THandle(-1)) then
  begin
    ProcInfo.dwSize := SizeOf(ProcInfo);
    if (Process32First(hSnapshot, ProcInfo)) then
    begin
      Memo1.Lines.Add(ProcInfo.szExeFile);
      while (Process32Next(hSnapShot, ProcInfo)) do
        Memo1.Lines.Add(ProcInfo.szExeFile);
    end;
    CloseHandle(hSnapShot);
  end;
end;

И octomail.exe в списке не оказалось! То есть, я не могу основываясь на списке процессов найти свою программу (по крайней мере пользуясь единственным известным мне способом - через родительный .exe-файл)



Aniskin
The Desktop Window Manager assists by seamlessly...

Правильно ли я понял, что в роли DWM в данном случае выступает explorer? Операционная система (в моём случае это вообще ХР) передала ему права на управление зависшим окном?
Может тогда в WinAPI есть какой-то другой вариант вместо GetWindowThreadProcessID, который указал бы на РЕАЛЬНЫЙ процесс моей программы, а не подставной временно подменяющий моё окно?
Ведь в случае если я пытаясь убить свою программу ошибочно убиваю процесс explorer.exe, и тотчас пытаясь повторить это же действие - то уже реально убиваю свою программу!
На самом деле я совершенно случайно пришёл к этом результату. Я написал код для уничтожения:
procedure TForm1.Button5Click(Sender: TObject);
var H,ProcID:Integer;
begin
H := FindWindow(nil, 'octoMail');
GetWindowThreadProcessId (H, @ProcID);
TerminateProcess( OpenProcess(PROCESS_TERMINATE,False,ProcID), 0);
end;

Нажал один раз - но вместо моей программы перезапустился Рабочий стол (explorer)
Нажал второй раз - опять перезапустился Рабочий стол
Нажал третий - и опять тоже самое...
И уже в отчаянии разочарованно хотел стукнуть последний раз, но палец случайно соскочил с мышки и нажал два раза (но чуть с большей паузой чем DblClick), и о чудо - закрылся Рабочий стол и моя программа... (explorer после этого конечно стартанул заново)

То есть, когда не запущен explorer (которому как я понял DWM передал процесс над зависшей программой), то GetWindowThreadProcessID возвращает реальный PID программы, а если explorer запущен, то GetWindowThreadProcessID возвращает PID Рабочего стола.
2 июл 19, 18:06    [21919330]     Ответить | Цитировать Сообщить модератору
 Re: WinAPI: как по ProcessID получить дексрикторы окон программы?  [new]
_Vasilisk_
Member

Откуда: Украина, Харьков
Сообщений: 11196
InterSky
В реальности же, ещё не зная как определить "Зависание", я начал искать по какому критерию могу это обнаружить
if SendMessageTimeout(...) = ERROR_TIMEOUT then
2 июл 19, 18:21    [21919342]     Ответить | Цитировать Сообщить модератору
 Re: WinAPI: как по ProcessID получить дексрикторы окон программы?  [new]
Aniskin
Member

Откуда:
Сообщений: 320
InterSky,

Когда приложение зависает (критерий зависания - результат IsHungAppWindow), то система создает фейковое окно, что бы юзер мог потыкать на кнопочки на заголовке окна. Это окно имеет то же имя, что и зависшее. И твой FindWindow(nil, 'octoMail') находит фейковое окно, принадлежащее Explorer, соответственно Explorer ты и убиваешь. Расширь критерии поиска до FindWindow('ПравильныйКлассОкна', 'octoMail').
2 июл 19, 18:23    [21919344]     Ответить | Цитировать Сообщить модератору
 Re: WinAPI: как по ProcessID получить дексрикторы окон программы?  [new]
InterSky
Member

Откуда:
Сообщений: 575
Если кому интересно, то сразу скажу что поскольку (как говорил ранее) зависания редкие, а в случае если это произошло то надо как можно скорей перезапустить программу, и поиграться нет возможности, то я вынужден был порыться в интернете и найти как можно больше способов как определить - "Зависла ли программа?", и все их реализовать заранее.
По мимо уже озвученных ранее:
1) Проверка какое родительское имя .EXE-файла показывает процесс.
2) Проверка наличия этого имени .EXE-файла в списке процессов (о котором сказал Leonid Kudryavtsev)
я так же сделал
3) Проверка количества наследников у главного окна GetWindow(H,GW_CHILD)
4) Проверка SendMessageTimeout (о котором сказал _Vasilisk_)
5) Проверка IsHungAppWindow (о котором сказал Aniskin)

Результаты:
1) Родительский .exe показывает - explorer.exe
2) Имени octomail.exe в списке процессов нет
3) Потомков у главного окна нет
4) SendMessageTimeout говорит что программа работает
5) IsHungAppWindow говорит что программа зависла

В сети везде рекомендовали проверять на зависание через SendMessageTimeout, но в одном месте кто-то написал что Windows определяет зависло ли программное приложение по IsHungAppWindow. Как видите - результат они выдали разный!

Теперь я хотел бы сказать очень большое спасибо Aniskin, потому что его полностью объяснила аномальное (как мне казалось) поведение зависшей программы:
1) я нашёл фейковое окно и получил его имя родительского .exe
2) тут вопрос (может раз зависло, то и нет в списке, так как не отзывается)
3) у фейкового окна нет потомков, по этому их 0
4) фейковое окно не зависло и по этому отвечает на SendMessageTimeout
5) тут вопрос (может и фейковое окно получает статус IsHungAppWindow)

Так что вопрос как бы чуточку сохраняется, почему обращаясь даже к фейковому окну я получаю IsHungAppWindow(H) = True ?
Я подтвержу слова Aniskin, что действительно не знаю обращаюсь я к моему окну или фейковому (как-то до этого всегда замечал что FindWindow первым показывает последнее запущенное при наличии нескольких вариантов и по этому логично предположить что именно фейковое и нахожу), но точно, что SendMessageTimeout и IsHungAppWindow запрашиваются с одним и тем же дескриптором.

Эххх...
Всё бы хорошо и складно, если бы не одно - ...and adding "Not Responding" to the title bar...
Проверил на старом браузере, который подвисает на некоторых страницах:
- Старое окно остаётся как есть (ежесекундно проверяю его декриптор, заголовок окна ', класс окна и PID)
Заголовок: "Входящие — Яндекс.Почта - Mozilla Firefox"
имя класса: MozillaWindowClass
- как только зависате появляется новое окно
Заголовок: "Входящие — Яндекс.Почта - Mozilla Firefox (Not Responding)"
имя класса: Ghost
Выходит не могу я отлавливать фэйковое окно, ведь у него другой заголовок?

Тут правда есть ещё один нюанс? Браузер как бы виснет. Но если ничего не делать - фейковое окно не появляется. Оно появится только в том случае если я несколько раз ткну в браузер мышкой! Вот теперь система вынуждена ответить что окно зависло и создаёт фейковое. Но фейковое имеет другой заголовок, с фразой - " (Not Responding)" вконце.

Я подумал, а может ли узнать виндоус что окно зависло если в него не тыкать?
С интервалом в 1 секунду натравил на браузер SendMessageTimeout и IsHungAppWindow.
- SendMessageTimeout десятки раз показывал что окно то висит то не висит.
- IsHungAppWindow показал что за время загрузки станицы окно подвисало 2 раза.
- при IsHungAppWindow=True всегда и SendMessageTimeout=True
- если IsHungAppWindow показывал что висим - первый же клик мышки создавал фейковое окно, но если не нажимать, то и окно не появится.

Тогда для проверки гипотезы №5, я решил наравить SendMessageTimeout и IsHungAppWindow на фейковое окно, и она подтвердилась: у фейкового окна IsHungAppWindow=True всегда а вот SendMessageTimeout=False

Но всё равно выходит что не мог я находить фейковое окно через FindWindow(nil, 'octoMail'), так как фейковое можно найти только через FindWindow(nil, 'octoMail (Not Responding)')
2 июл 19, 21:55    [21919488]     Ответить | Цитировать Сообщить модератору
 Re: WinAPI: как по ProcessID получить дексрикторы окон программы?  [new]
Aniskin
Member

Откуда:
Сообщений: 320
InterSky,

> 1) Родительский .exe показывает - explorer.exe

А кто в норме родитель?

> Всё бы хорошо и складно, если бы не одно - ...and adding "Not Responding" to the title bar...

Да, тут я был не прав, я не учел строку (Not Responding). Я сделал простой эксперимент - создал свое приложение из одной формы с вечным циклом While True do; тем самым получил зависшее окно. И FindWindow его находит вполне корректно.

> Оно появится только в том случае если я несколько раз ткну в браузер мышкой!

Тут все правильно, зачем пользователя нервировать если окно ему не нужно. Может быть оно само отремонтируется через время.
3 июл 19, 05:49    [21919555]     Ответить | Цитировать Сообщить модератору
 Re: WinAPI: как по ProcessID получить дексрикторы окон программы?  [new]
AlexeyM123
Member

Откуда:
Сообщений: 158
Добрый день всем,
В продолжении темы подскажите в каком направлении копать.

Имеется старое Intraweb 7 приложение как Standalone.
Раньше работало стабильно на сервере Windows 2003.
Теперь перенесено на виртуалку и Windows Server 2008 и временами отваливается.
Доступ к сервису только внутри сети и через VPN.
Если через Task Manager убить и перезапустить процесс, то до времени работает нормально.

Пишу костыль перезапуска.

Сделал проверку зависания на класс TFormIWMain по процедуре https://www.swissdelphicenter.ch/en/showcode.php?id=910
Не знаю правильно ли, сервис же multithreaded

Или же надо сделать проверку через TNetHTTPClient методом Get и проверять StatusCode в HTTP Response ?
4 июл 19, 12:40    [21920677]     Ответить | Цитировать Сообщить модератору
 Re: WinAPI: как по ProcessID получить дексрикторы окон программы?  [new]
InterSky
Member

Откуда:
Сообщений: 575
AlexeyM123
Пишу костыль перезапуска.

Сделал проверку зависания на класс TFormIWMain по процедуре https://www.swissdelphicenter.ch/en/showcode.php?id=910
Не знаю правильно ли, сервис же multithreaded

Так при первом же зависании узнаешь - откликается или нет.
Хотя как я писал раньше:
автор
В сети везде рекомендовали проверять на зависание через SendMessageTimeout, но в одном месте кто-то написал что Windows определяет зависло ли программное приложение по IsHungAppWindow. Как видите - результат они выдали разный!

Конкретно в моём случае - твой вариант показывал что программа работает! (хотя она была зависшей, и может тоже какой-то поток работал, а интерфейс висел)


AlexeyM123
Или же надо сделать проверку через TNetHTTPClient методом Get и проверять StatusCode в HTTP Response ?

Как видишь, есть чуть ли не десятки вариантов за что зацепиться чтобы понять что программа не работает. Если один вариант не работает, то ищи другой. Но если ты нашёл как надёжно определить одним вариантом - то зачем искать другие? Хотя в твоём случае я бы наверно в первую очередь проверял отклик по HTTP-протоколу (и причём делал бы это через telnet.exe)
4 июл 19, 15:56    [21920865]     Ответить | Цитировать Сообщить модератору
 Re: WinAPI: как по ProcessID получить дексрикторы окон программы?  [new]
AlexeyM123
Member

Откуда:
Сообщений: 158
InterSky

Я вдохновился твоей статьей, когда делал костыль.
Сейчас, как назло приложение не отваливается, так что не могу определить , какой подход лучше.
спасибо за совет о Telnet.
4 июл 19, 17:03    [21920960]     Ответить | Цитировать Сообщить модератору
 Re: WinAPI: как по ProcessID получить дексрикторы окон программы?  [new]
Ghost Writer
Member

Откуда: Россия
Сообщений: 830
InterSky
Вы же предлагаете написать 17 строк чтобы перебрать все процессы через Process32Next, потом ещё как-то определить какой из них мой, и говорите что не понимаете меня почему я не делаю вашим образом?
ну если окно с именем octomail однозначно Ваше, то почему процесс с именем octomail.exe не Ваш ??? странно.


InterSky
И octomail.exe в списке не оказалось!
что, реально ? крепко зависла. не очень верю.
и Диспетчер задач/ Process Explorer Руссиновича в моменты зависания тоже не видят и не могут прибить ?
4 июл 19, 23:05    [21921194]     Ответить | Цитировать Сообщить модератору
 Re: WinAPI: как по ProcessID получить дексрикторы окон программы?  [new]
Gator
Member

Откуда: Москва
Сообщений: 14978
Вроде ТСу всё разъяснили уже, а воз и ныне там.
Ну и я перефразирую задачу. Как найти хендл окна, зная PID? (Особенно, если у процесса нет окон Картинка с другого сайта.)

Фигня вопрос!

var
  h: HWND;
  pid: DWord;

begin
   h := FindWindowEx(0, 0, nil, nil);
   while (h <> 0) do
   begin
     GetWindowThreadProcessId(h, @pid);
     if (pid = pi.dwProcessId) then // my window is my thread
        h := FindWindowEx(0, h, nil, nil);
   end;
end;
5 июл 19, 02:14    [21921253]     Ответить | Цитировать Сообщить модератору
 Re: WinAPI: как по ProcessID получить дексрикторы окон программы?  [new]
Мимопроходящий
Member

Откуда: бурятский тундрюк, эсквайр
Сообщений: 30502

05.07.2019 2:14, Gator пишет:
> Фигня вопрос!

и решение тоже.
ты ищешь первое попавшееся окно с этим PID-ом.
а оно ли тебе надо?

Posted via ActualForum NNTP Server 1.5

5 июл 19, 10:37    [21921397]     Ответить | Цитировать Сообщить модератору
Все форумы / Delphi Ответить