27 января 2025 г.

Отключение визуальных диалогов об ошибках для фоновых приложений

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

Но иногда по каким-то причинам вы можете захотеть сделать фоновое приложение в виде обычного пользовательского приложения. В этом случае вы не хотели бы, чтобы ваше приложение показывало какие-либо сообщения об ошибках.

Тестовый код

Чтобы проверить поведение вашего приложения при фатальных ошибках - вы можете использовать такой код:
// Функция потока
function Crash(Arg: Pointer): Integer; stdcall;
begin
  // Искуственно вызываем тестовое исключение
  PInteger(nil)^ := 0;
  Result := 0; 
end;

function TForm1.Button1Click(Sender: TObject);
var
  TID: Cardinal;
begin
  // Создаём новый поток с указанной выше точкой входа
  CloseHandle(CreateThread(nil, 0, @Crash, nil, 0, TID));
end;
Этот код создаёт новый поток кода Crash, используя системную функцию CreateThread. Потоки кода, создаваемые CreateThread, не имеют никаких обработчиков исключений, поэтому если такой поток возбуждает исключение, а код вашей функции потока его не обрабатывает, это исключение передаётся системе (глобальному обработчику), что и приводит к фатальному исключению.
В данном случае наш тестовый код возбуждает исключение EAccessViolation, пытаясь записать число (0) по нулевому указателю (nil).

Программа с EurekaLog

Если в вашем проекте есть EurekaLog, то поведение программы будет иным, поскольку EurekaLog установит свой глобальный обработчик исключений. Получить фатальное исключение в проекте с EurekaLog значительно сложнее, поэтому для тестов EurekaLog лучше отключить.

Замечу, что добавление в программу EurekaLog не гарантирует вам, что показанный выше диалог никогда не появится для вашей программы. EurekaLog работает изнутри процесса. Это означает, что всегда будет вероятность возникновения настолько плохой ситуации, что обработчики исключений внутри программы не могут быть вызваны, что приведёт к закрытию процесса извне (системой). Например:
// Вызывает тестовое исключение
procedure Kaboom;
begin
  raise Exception.Create('Error Message');
end;

// Портит стек CPU и вызывает функцию Kaboom
procedure Test; assembler;
asm
  // Эмуляция "buffer overflow"
  mov [esp+4],  0;
  mov [esp+8],  0;
  mov [esp+12], 0;

  call Kaboom;
end;

// Функция тестового потока
function T(I: Integer): Integer; stdcall;
begin
  try
    Test;
  except
    // Не важно
  end;
  Result := 0;
end;
Данный код приведёт к "вылету" приложения даже с EurekaLog на борту, поскольку этот код портит системные записи обработчиков исключение. Поэтому, когда функция Kaboom возбуждает тестовое исключение, система не может вызвать обработчик исключений (помечен как "Не важно" в примере кода) и ей ничего не остаётся как закрыть процесс.

Отключение системных отчётов

Итак, чтобы ваше приложение не показывало бы диалог "Прекращена работа программы", вы можете отключить для своего приложения службу WER (Windows Error Reporting). Сделать это можно, вызвав такой код при старте вашей программы:
SetErrorMode(GetErrorMode or SEM_NOGPFAULTERRORBOX);
Функция SetErrorMode задаёт как система или процесс должны обрабатывать фатальные ошибки. В частности, флаг SEM_NOGPFAULTERRORBOX указывает, что система не должна вызывать отчеты об ошибках Windows.

Заметьте, что вы обязаны модифицировать уже заданный режим работы процесса, его нельзя перезаписывать полностью с нуля, убирая флаги, про которые вы ничего не знаете. Например, такой код - не корректен:
SetErrorMode(SEM_NOGPFAULTERRORBOX);
Если же вам нужно выключить этот режим, то вы должны использовать такой код:
SetErrorMode(GetErrorMode and (not SEM_NOGPFAULTERRORBOX));
Замечу, что включение указанного режима полностью отключит службу WER (Windows Error Reporting) для вашей программы.

Отключение только визуальных диалогов

Если же вы не хотите полностью отключать WER для вашего приложения (например, если вы хотите использовать системные логи WER для просмотра списка "вылетов" вашего приложения), вы можете отключить только визуальные диалоги. Сделать это можно вызвав такой код:
var
  Flags: Cardinal;
begin
  if Failed(WerGetFlags(GetCurrentProcess, Flags)) then
    Flags := 0;
  WerSetFlags((Flags or WER_FAULT_REPORTING_NO_UI) and (not WER_FAULT_REPORTING_ALWAYS_SHOW_UI));
Функция WerSetFlags задает параметры отчеты об ошибках Windows (WER) для текущего процесса.

Как и в случае с SetErrorMode, вам не следует вызывать WerSetFlags вслепую, перезаписывая уже установленные режимы работы, вам нужно изменять только те режимы, которые вы хотите поменять. В частности, данный код из примера выше устанавливает флаг WER_FAULT_REPORTING_NO_UI и убирает флаг WER_FAULT_REPORTING_ALWAYS_SHOW_UI. Флаг WER_FAULT_REPORTING_NO_UI сообщает WER никогда не показывать пользовательский интерфейс отчетов об ошибках для этого процесса. Флаг же WER_FAULT_REPORTING_ALWAYS_SHOW_UI, наоборот, просит WER всегда показывать пользовательский интерфейс отчетов об ошибках для этого процесса.

Функция WerSetFlags и флаги WER_FAULT_REPORTING_NO_UI, WER_FAULT_REPORTING_ALWAYS_SHOW_UI не объявлены в стандартных заголовочных файлах Delphi, но вы можете объявить их самостоятельно, импортировать из JEDI Windows API Library или из EurekaLog. Например (для EurekaLog):
uses
  EWER;

var
  Flags: Cardinal;
begin
  // Нужно вызвать один раз перед использованием модуля EWER
  InitWER; // импортирует функции
  
  // Будет False для очень старых Windows (2000 и XP)
  if Assigned(WerSetFlags) then
  begin
    if Failed(WerGetFlags(GetCurrentProcess, Flags)) then
      Flags := 0;
    WerSetFlags((Flags or WER_FAULT_REPORTING_NO_UI) and (not WER_FAULT_REPORTING_ALWAYS_SHOW_UI));
  end;

Отключение диалогов в EurekaLog

Если в ваш проект добавлена EurekaLog, то она тоже может показывать диалоги об ошибках. Вы можете отключить эти диалоги. Во-первых, конечно же, вы должны переключить в настройках проекта основной визуальный диалог в позицию "None" (отключить диалог).

Если у вас в проекте настроена отправка отчётов, то вам следует отключить визуальное сопровождение отправки отчётов, отключив опции "Show send progress", "Show success message", "Show failure message".

Наконец, в некоторых редких случаях EurekaLog может показывать простые сообщения (через MessageBox). Эти диалоги также можно отключить. Для этого в настройках обработчиков нужно включить установку обработчиков для невизуальных приложений ("[Non-visual] Hides MessageBoxes"). Эта опция включается автоматически, если при начальной настройке проекта под EurekaLog вы выбираете подходящий профиль (например, системной службы), но если вы выбираете стандартный профиль "приложения VCL Forms", то эту настройку нужно включать вручную.

P.S. Если вы разрабатываете службу, то, вероятно, вы захотите использовать диалог записи в системный лог вместо полного отключения диалога.

P.P.S. Поскольку вы пишете фоновое приложение, которое работает как стандартное пользовательское приложение, системные настройки перезапуска не будут применены к вашему приложению. Поэтому оно будет просто завершено в случае сбоя. Вы можете настроить настройки перезапуска в EurekaLog, но вы должны понимать, что эти настройки будут работать только в том случае, если ваше приложение завершит работу более или менее корректно (под управлением EurekaLog). Например, выше у нас был пример кода, который иллюстрирует, как приложение с EurekaLog может вылететь с фатальным сбоем. В этом случае ваше приложение будет закрыто внешним процессом (системой), так что любой пользовательский код из вашего процесса не будет вызываться (включая EurekaLog). Это означает, что если вам нужен надёжный способ перезапуска вашего приложения, вам нужно иметь какой-то внешний процесс-монитор, который перезапустит ваше приложение в случае фатального сбоя.

Комментариев нет :

Отправить комментарий

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и (опционально) ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку.

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.

Примечание. Отправлять комментарии могут только участники этого блога.