Как поймать исключение в событии TDataModule.OnCreate?

Я столкнулся со следующей проблемой в Delphi с блоком try/except.

У меня есть простое приложение - одна MainForm с именем fr_MAIN и одна TDataModule с именем DM. DM не создается автоматически, но создается во время выполнения в событии fr_MAIN Button2.OnClick:

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 имеет некоторый код в своем событии OnCreate:

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

Проблема в том, что когда я нажимаю Button2, я получаю сообщение об исключении 'this is error!', остальная часть DM code here не запускается - что правильно! Но затем я также получаю сообщение 'DM started!' вместо сообщения 'DM not started!'.

Исключение, вызванное DM, прерывает действие, но не перехватывается в блоке except формы!

Почему это?

Ответы

Ответ 1

TDataModule 1 имеет специальную обработку исключений, выраженных в событии OnCreate.

Исключение обрабатывается здесь:

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;

По умолчанию TApplication присваивает TApplication.HandleException() 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;

Итак, TDataModule.DoCreate() перехватывает исключение и передает его на TApplication.HandleException(), который затем отображает всплывающее диалоговое окно по умолчанию. И так как TDataModule.HandleCreateException() затем возвращает True, исключенное исключение не будет повторно поднято. Исключение теперь считается обработанным, что позволяет программе нормально продолжать свой вызов Showmessage('DM started!');.

Чтобы избежать всплывающего диалогового окна при возникновении исключения, вы можете назначить обработчик события TApplication.OnException:

Vcl.Forms.TApplication.OnException

Использовать OnException для изменения поведения по умолчанию, которое происходит, когда исключение не обрабатывается кодом приложения. Событие OnException обработчик вызывается автоматически в методе HandleException.

Но исключение все равно будет уловлено и отклонено TDataModule.DoCreate(). Если вы хотите этого избежать, поэтому исключение распространяется на стек вызовов, не поднимайте исключение из события TDataModule.OnCreate вообще. Переопределите виртуальный конструктор TDataModule.Create() и вместо этого создайте исключение.

1: То же самое происходит и в TCustomForm.

Ответ 2

Лучшее решение - это исправить все формы везде.

Скопируйте Forms.pas из папки \ Vcl\Source в папку вашего проекта (или в общую папку общей библиотеки, чтобы все проекты извлекли из нее пользу).

Затем измените TCustomForm.HandleCreateExcpetion на:

function TCustomForm.HandleCreateException: Boolean;
begin
{
        If an exception is raised during a form 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;

Если вы используете более ранние версии Delphi, HandleCreateException отсутствует. Вы должны исправить вызывающего непосредственно:

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;