Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to catch an Exception in a TDataModule.OnCreate event?

I encountered the following issue in Delphi with a try/except block.

I have a simple application - one MainForm named fr_MAIN and one TDataModule named DM. DM is not auto-created, but it is created at run-time in fr_MAIN's Button2.OnClick event:

procedure Tfr_MAIN.Button2Click(Sender: TObject);
begin
  try
    DM := TDM.Create(nil);
    Showmessage('DM started!');
  except
    on E:Exception do
    begin
      Showmessage('DM not started!');
    end;
  end;

DM has some code in its OnCreate event:

procedure TDM.DataModuleCreate(Sender: TObject);
begin
  raise Exception.Create('this is error!');
  // DM code here ...
end;

The problem is that when I click on Button2, I receive the 'this is error!' exception message, the rest of the DM code here does not run - which is correct! But then I also receive the 'DM started!' message instead of the 'DM not started!' message.

The exception raised by DM interrupts the action, but is not caught in the except block of the form!

Why is this?

like image 428
altink Avatar asked Oct 31 '17 20:10

altink


2 Answers

TDataModule1 has special handling of exceptions raised in its OnCreate event.

The exception is handled here:

procedure TDataModule.DoCreate;
begin
  if Assigned(FOnCreate) then
  try
    FOnCreate(Self);
  except
    if not HandleCreateException then // <-- here
      raise;
  end;
end;

function TDataModule.HandleCreateException: Boolean;
begin
  if Assigned(ApplicationHandleException) then
  begin
    ApplicationHandleException(Self); // <-- here
    Result := True;
  end
  else
    Result := False;
end;

By default, TApplication assigns TApplication.HandleException() to ApplicationHandleException:

constructor TApplication.Create(AOwner: TComponent);
var
  ...
begin
  inherited Create(AOwner);
  ...
  if not Assigned(System.Classes.ApplicationHandleException) then
    System.Classes.ApplicationHandleException := HandleException; // <-- here
  if not Assigned(System.Classes.ApplicationShowException) then
    System.Classes.ApplicationShowException := ShowException;
  ...
end;

So, TDataModule.DoCreate() is catching the exception and passing it to TApplication.HandleException(), which then displays a popup dialog by default. And since TDataModule.HandleCreateException() then returns True, the caught exception is not re-raised. The exception is now considered handled, allowing the program to continue normally to its Showmessage('DM started!'); call.

To avoid the popup dialog when the exception is raised, you can assign a TApplication.OnException event handler:

Vcl.Forms.TApplication.OnException

Use OnException to change the default behavior that occurs when an exception is not handled by application code. The OnException event handler is called automatically in the HandleException method.

But the exception is still going to be caught and dismissed by TDataModule.DoCreate(). If you want to avoid that, so the exception propagates up the call stack, don't raise the exception from the TDataModule.OnCreate event at all. Override the virtual TDataModule.Create() constructor and raise the exception from there instead.

1: The same thing also happens in TCustomForm.

like image 59
Tom Brunberg Avatar answered Oct 13 '22 01:10

Tom Brunberg


The better solution is to fix it for all forms everywhere.

Copy Forms.pas from the \Vcl\Source folder into either your project folder (or into a common shared library folder so that all projects will benefit from it).

Then change TCustomForm.HandleCreateExcpetion to:

function TCustomForm.HandleCreateException: Boolean;
begin
{
        If an exception is raised during a form's OnCreate event, the exception is hidden.
        This leaves you with an only partially initialized form.

        The correct behavior is to **not** eat the exception.

        We do that by returning False. The caller will then throw.
}
//  Application.HandleException(Self);
//  Result := True;
    Result := False;
end;

If you're on earlier versions of Delphi, there is no HandleCreateException. You have to fix the caller directly:

procedure TCustomForm.DoCreate;
begin
{
        If the Form.OnCreate event throws an exception, the exception is eaten, and the caller never knows about it.

        Don't do that.
}
    if Assigned(FOnCreate) then
    begin
        //try
            FOnCreate(Self);
        //except
        //  Just let it throw. Christ you guys are dense.
            //Application.HandleException(Self);
        //end;
    end;

    if fsVisible in FFormState then
        Visible := True;
end;
like image 36
Ian Boyd Avatar answered Oct 13 '22 03:10

Ian Boyd