27 January, 2025

Disable visual error dialogs for background apps

Typically, background applications run as system services. In this case, fatal errors cause the application to close and possibly restart automatically if specified in the service's settings.

But sometimes, for some reason, you may want to make a background application as a regular user application. In this case, you would not want your application to show any error messages.


Test code

To test your application's behavior with fatal errors - you can use the following code:
// A thread function
function Crash(Arg: Pointer): Integer; stdcall;
begin
  // Artificially raise a test exception
  PInteger(nil)^ := 0;
  Result := 0; 
end;

function TForm1.Button1Click(Sender: TObject);
var
  TID: Cardinal;
begin
  // Create a new thread with the entry point specified above
  CloseHandle(CreateThread(nil, 0, @Crash, nil, 0, TID));
end;
This code creates a new Crash thread using the CreateThread system function. Threads created by the CreateThread function do not have any exception handlers, so if such a thread throws an exception and your thread's function code does not handle it, the exception is propagated to the system (a global handler), which results in a fatal exception.
In this case, our test code throws an EAccessViolation exception, trying to write an integer (0) to a null pointer (nil).


Apps with EurekaLog

If your project has EurekaLog on board, your application will behave differently, because EurekaLog will install its own global exception handler. It is much more difficult to get a fatal exception in a project with EurekaLog, so it is better to disable EurekaLog for testing.

Note that adding EurekaLog to your application does not guarantee that the dialog shown above will never appear for your application. EurekaLog runs from within the process. It means that there will always be a chance of a situation so bad that exception handlers inside the application cannot be called, which will lead to the process being closed from outside (by the system). For example:
// Calls a test exception
procedure Kaboom;
begin
  raise Exception.Create('Error Message');
end;

// Damages CPU stack and calls the Kaboom function
procedure Test; assembler;
asm
  // We simulate a "buffer overflow" bug
  mov [esp+4],  0;
  mov [esp+8],  0;
  mov [esp+12], 0;

  call Kaboom;
end;

// A test thread's function
function T(I: Integer): Integer; stdcall;
begin
  try
    Test;
  except
    // Does not matter
  end;
  Result := 0;
end;
This code will crash the application even with EurekaLog on board, since this code corrupts the system records of exception handlers. Therefore, when the Kaboom function raises a test exception, the system cannot call the exception handler (marked as "Does not matter" in the code example) and has no choice but to close the process.


Disabling system reports

So, to prevent your application from showing the "Program has stopped working" dialog, you can disable the WER (Windows Error Reporting) service for your application. You can do this by calling the following code when your application starts:
SetErrorMode(GetErrorMode or SEM_NOGPFAULTERRORBOX);
The SetErrorMode function specifies how the system or process should handle fatal errors. In particular, the SEM_NOGPFAULTERRORBOX flag specifies that the system should not generate Windows error reports.

Note that you must modify the already defined process mode, you cannot rewrite it completely from scratch, removing flags that you know nothing about. For example, the following code is WRONG:
SetErrorMode(SEM_NOGPFAULTERRORBOX);
If you need to turn off this mode, you should use a code like this:
SetErrorMode(GetErrorMode and (not SEM_NOGPFAULTERRORBOX));
Note that enabling this mode will completely disable the WER (Windows Error Reporting) service for your application.


Disabling only visual dialogs

If you do not want to completely disable WER for your application (for example, if you want to use the WER system logs to view the list of "crashes" of your application), you can disable only visual dialogs. This can be done by calling the following code:
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));
The WerSetFlags function sets the Windows Error Reporting (WER) options for the current process.

As with SetErrorMode, you should not call WerSetFlags blindly, overriding already set modes of operation, you should only change those modes that you want to change. Specifically, the code in the example above sets the WER_FAULT_REPORTING_NO_UI flag and clears the WER_FAULT_REPORTING_ALWAYS_SHOW_UI flag. The WER_FAULT_REPORTING_NO_UI flag tells WER to never show the error reporting user interface for this process. The flag WER_FAULT_REPORTING_ALWAYS_SHOW_UI, on the contrary, asks WER to always show the error reporting user interface for this process.

The WerSetFlags function, and the WER_FAULT_REPORTING_NO_UI, WER_FAULT_REPORTING_ALWAYS_SHOW_UI flags are not declared in the standard Delphi header files, but you can declare them yourself, import them from the JEDI Windows API Library or from EurekaLog. For example (for EurekaLog):
uses
  EWER;

var
  Flags: Cardinal;
begin
  // You should call this function before using anything from EWER unit
  InitWER; // imports WER functions
  
  // Will be False for very old Windows (like 2000 and 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;


Disabling dialogs in EurekaLog

If EurekaLog is added to your project, it can also show dialogs about errors. You can disable these dialogs. First, of course, you should switch the main visual dialog to "None" in the project settings (disables the exception dialog).

If you have configured sending reports in your project, then you should disable visual support for sending reports by disabling the "Show send progress", "Show success message", and "Show failure message" options.

Finally, in some rare cases, EurekaLog can show simple messages (via MessageBox). These dialogs can also be disabled. To do this, you need to enable hooks for non-visual applications ("[Non-visual] Hides MessageBoxes") in the hooks settings. This option is enabled automatically if you select a suitable profile (for example, a system service) during the initial project setup for EurekaLog, but if you select the standard "VCL Forms application" profile, you need to enable this setting manually.

Important note: technically there is a callback in EurekaLog, which implements a MessageBox. Every time EurekaLog wants to show a simple message, this callback will be called. This callback can be set to show a visual dialog (like the usual MessageBox), it can be set to output to the concole, it can be set to do nothing. The "None" dialog sets the "[Non-visual] Hides MessageBoxes" setting, which sets the callback to do nothing. While the "MS Classic" dialog sets the callstack to show visual message box. Obviosly, you can't have both at the same time - you have either one or another.


Mixed mode apps or exceptions

Sometimes your application has to work in two modes:
  1. Visual. For example, when launched by a user from the Start menu.
  2. Non-visual. For example, when launched as a service or from autorun/Task Sheduler.
And sometimes your application wants to handle exceptions differently (even if it was visually launched by a user):
  1. Show exception. For example, when an exception happens in the main (UI) thread.
  2. Hide exception. For example, when an exception happens in a service background thread.
If this is the case - you would need to switch at run-time between being visual and hiding visuals.

First, you need to configure your application to be visual - set dialog to an appropriate option (such as "MS Classic"). Enable all other visual behaviour, like if your app would be run by a user.

Once you set up your application to behave correctly when interacting with a user - write some code to turn off visual behaviour at run-time. For example, if you write a mixed mode app, you can decice how it should run at startup. If it should run in the visual mode - do nothing. If it should run in non-visual mode - turn off visuals:
initialization
  if RunAsService then // your function to decide how to launch your app
  begin
    CurrentEurekaLogOptions.ExceptionDialogType := edtNone;
    CurrentEurekaLogOptions.sndSendInSeparatedThread := False;
    CurrentEurekaLogOptions.sndShowSendDialog := False;
    CurrentEurekaLogOptions.sndShowSuccessMsg := False;
    CurrentEurekaLogOptions.sndShowSuccessBugClosedOnlyMsg := False;
    CurrentEurekaLogOptions.sndShowFailureMsg := False;
    CurrentEurekaLogOptions.boSaveCompressedCopyInCaseOfError := False;
    CurrentEurekaLogOptions.boCopyLogInCaseOfError := False;
    CurrentEurekaLogOptions.sndScreenshot := ssNone;
  end;
end.
Please see: How to change EurekaLog's settings at run-time?

If you write an app, which handles various exceptions differently - then you have to decide which exceptions you want to hide. For example:
uses
  EEvents; // for RegisterEventExceptionNotify
 
// Custom handler to customize exception's handling 
procedure HideBackgroundExceptions(const ACustom: Pointer; 
  AExceptionInfo: TEurekaExceptionInfo; 
  var AHandle: Boolean; 
  var ACallNextHandler: Boolean);
begin
  // If the exception is coming from a background thread
  // (this is only an example, you can/should replace it with your own code)
  if AExceptionInfo.ThreadID <> MainThreadID then
  begin
    // Disable all visual feedback for this exception
    AExceptionInfo.Options.ExceptionDialogType := edtNone;
    AExceptionInfo.Options.sndShowSendDialog := False;
    AExceptionInfo.Options.sndShowSuccessMsg := False;
    AExceptionInfo.Options.sndShowSuccessBugClosedOnlyMsg := False;
    AExceptionInfo.Options.sndShowFailureMsg := False;
    AExceptionInfo.Options.boSaveCompressedCopyInCaseOfError := False;
    AExceptionInfo.Options.boCopyLogInCaseOfError := False;
    AExceptionInfo.Options.sndScreenshot := ssNone;
    AExceptionInfo.Options.sndSendInSeparatedThread := False;
  end;  
end;
 
initialization
  // Ask EurekaLog to call our code for each exception
  RegisterEventExceptionNotify(nil, HideBackgroundExceptions);
end.


P.S. If you are developing a service, you will probably want to use the system logging setup dialog instead of disabling the dialog completely.

P.P.S. Since you are writing a background application which runs as a standard user application, system restart settings will not be applied to your application. So it will be simply terminated in case of a crash. You may set up restart settings in EurekaLog, but you should be aware that these settings will only work if your application shut downs more or less gracefully (under control of EurekaLog). For example, we had a code example above that illustrates how EurekaLog-enabled application can perform a fatal crash. Your application will be closed by an external process (the system) in that case, so any user code from within your process won't be called (including EurekaLog). It means that if you want a reliable way to restart your application - you have to have some sort of external monitor process, which will restart your application in case of fatal crash.

See also:

Download EurekaLog | Purchase License | Contact Support