5

I have set up a global exception handler in Delphi. On some severe exceptions an error message is displayed (followed by Halt()). While the error message is shown, Delphi is processing the message queue, processing timer events, that lead to further errors.

What I want is to show an error dialog which does not process timer events. How is that possible in Delphi?

Edit: I use Dialogs.MessageDlg(...) to display the message.

Alois Heimer
  • 1,772
  • 1
  • 18
  • 40
  • I think you should have some global flag "Final Error Message is displayed" and intercept App's OnMsg so that only messages addressed to that error dialog would pass through, other messages you would filter out http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/Forms_TApplication_OnMessage.html – Arioch 'The Nov 22 '13 at 11:37
  • 2
    If you use `TTimer`(s), you could recursively disable all `TTimer`(s) for the `Application` before you show the dialog. just a thought. – kobik Nov 22 '13 at 12:25
  • @kobik Then my global exception handler would need to be aware of every TTimer. I prefer the other solutions. – Alois Heimer Nov 22 '13 at 15:38

3 Answers3

4

You can filter queued messages, such as WM_TIMER, with TApplication.OnMessage.

procedure TMainForm.ApplicationMessage(var Msg: TMsg; var Handled: Boolean);
begin
  if ShowingFatalErrorDialog then
    if Msg.Message = WM_TIMER then
      Handled := True;
end;

Either assign that event handler directly to Application.OnMessage or use a TApplicationEvents object.

Obviously you'll have to provide the implementation for ShowingFatalErrorDialog but I trust that it is obvious to you how to do so.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • I was thinking of filtering the messages within the dialog. But with TApplicationEvents it will work this way without relying on global vars. Thanks. – Alois Heimer Nov 22 '13 at 12:18
  • I think black list approach is a dead end here. One should implement white list policy instead – Arioch 'The Nov 22 '13 at 12:18
  • @Arioch'The You meant to post that comment to the question I think. – David Heffernan Nov 22 '13 at 12:19
  • @AloisHeimer you don't have any warranty that your dialog would get all the messages. (Neither would TApplication in multithreaded apps, AFAICT). So that is why both me and David sugegst at intercepting at most global level of easily reachable - appliaction's main vcl thread level – Arioch 'The Nov 22 '13 at 12:20
  • @DavidHeffernan Well, i don't think that questions about "how should i shoot my leg" should be answered formally without hinting at better options – Arioch 'The Nov 22 '13 at 12:21
  • @There queued timer messages will be pulled off by the modal dialogs message loop. 100%. Who else is pulling them? – David Heffernan Nov 22 '13 at 12:28
  • I am about white/black listing. I mean there might be other deferred messages but WM_Timer that also can be harming and also better be filtered – Arioch 'The Nov 22 '13 at 12:36
  • @Arioch'The But how to white list "good" messages? That would be all drawing messages (?) - which could theoretically do further harm. The approach to handle the timer messages is good enough for me. – Alois Heimer Nov 22 '13 at 13:01
2

Try something like this:

    ...
  private
    FAboutToTerminate: Boolean;
  end;

...

type
  ESevereError = class(Exception);

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  Tag := Tag + 1;
  if Tag > 2 then
    raise ESevereError.Create('Error');
end;

procedure TForm1.ApplicationEvents1Exception(Sender: TObject;
  E: Exception);
begin
  if (E is ESevereError) and (not FAboutToTerminate) then
  begin
    FAboutToTerminate := True;
    Application.ShowException(E);
    Application.Terminate;
  end;
end;
NGLN
  • 43,011
  • 8
  • 105
  • 200
  • Thanks, I used your idea to swallow all exceptions in my final implementation. But my main focus was to not generate the fatal timer events in the first line. – Alois Heimer Nov 22 '13 at 13:51
  • 1
    I think the `FreeAndNil(E)`-part of your code is wrong. According to the documentation, I think, you are not supposed to free the exception. This lead to an [access violation](http://stackoverflow.com/q/22630972/2523663) for me. Have a look at [this explanation](http://stackoverflow.com/a/22668566/2523663). – Alois Heimer Mar 26 '14 at 17:53
  • Thanks! Maybe `ApplicationEvents.CancelDispatch` could help to prevent the AV, but not destroying the exception surely is way better! – NGLN Mar 26 '14 at 19:38
1

Just for reference: I will use the following code, which is a mixture from both answers.

procedure SaveShowErrorMessage(...)
begin
    with TFatalErrorAppEvents.Create(nil) do  //avoid timer and further exceptions
    try
      Dialogs.MessageDlg(...);
    finally
      Free;
    end;
end;

With TFatalErrorAppEvents as follows:

type
    TFatalErrorAppEvents = class(TApplicationEvents)
    protected
        procedure KillTimerMessages(var Msg: tagMSG; var Handled: Boolean);
        procedure IgnoreAllExceptions(Sender: TObject; E: Exception);
    public
        constructor Create(AOwner: TComponent); override;
    end;


constructor TFatalErrorAppEvents.Create(AOwner: TComponent);
begin
    inherited;
    OnMessage := KillTimerMessages;
    OnException := IgnoreAllExceptions;
end;

procedure TFatalErrorAppEvents.IgnoreAllExceptions(Sender: TObject; E: Exception);
begin
    //in case of an Exception do nothing here to ignore the exception 
end;

procedure TFatalErrorAppEvents.KillTimerMessages(var Msg: tagMSG; var Handled: Boolean);
begin
    if (Msg.message = WM_TIMER) then
      Handled := True;
end;
Alois Heimer
  • 1,772
  • 1
  • 18
  • 40