Delphi

КОМПЬЮТЕРНЫЕ КУРСЫ "ПОИСК"

[Главная страница] [Delphi] [Контакты]

Исключительные ситуации и обработка исключений


Разрабатывая какое-нибудь приложение, вы должны написать код, который будет решать поставленную задачу, а также код, который будет выполнять проверку на наличие ошибок. Как правило, код для обработки ошибок строится на основе оператора if. Оператор if часто используется для проверки данных, вводимых пользователем, а также результатов выполнения функций. В простых алгоритмах можно ограничиться применением оператора if, однако в приложениях с графическим интерфейсом пользователя, где пользователи имеют полную свободу действий, ошибки могут возникать когда угодно и где угодно. Использование одного только оператора if для защиты приложения — не самая лучшая идея.

С задачей перехвата ошибок и реагирования на них лучше всего справляется механизм обработки исключений (Структурированная обработка исключений (Structured exception handling – SHE) представляет собой метод обработки ошибок, благодаря которому можно восстановить нормальную работу приложения после сбоя в работе программы, который в противном случае был бы фатальным). Если в приложении, написанном с помощью Delphi, возникает ошибка, то приложение автоматически генерирует исключение. Исключением представляет собой объект, который описывает возникающую ошибку.

Генерация исключения означает всего лишь то, что приложение создало объект исключения и максимально подробно описало ошибку. Если мы не обрабатываем исключение (то есть не приготовлен специальный код для перехвата исключения), приложение само сделает это автоматически. Обычно приложение обрабатывает исключение, выводя на экран монитора окно с сообщением о возникшей ошибке. Например, если вы передадите функции StrToInt строку, содержащую пустую строку, то функция сгенерирует исключение.

procedure TForm1.Button1Click(Sender: TObject);
var
  x: Integer;
begin
  x := StrToInt(Edit1.Text);
end;

Чтобы обработать исключение, сгенерированное функцией StrToInt, мы должны поместить вызов функции StrToInt в защищенный блок кода. Защищенным является блок кода, который может реагировать на некоторое исключение. В Delphi защищенный блок выглядит следующим образом:

try
    оператор(ы)
except
    операторы обработки исключения
end;

Операторы, которые могут сгенерировать исключение, записываются в блоке try, а в обработчике исключений пишется код, который занимается обработкой исключений. Обработчик исключения является частью защищенного блока, начинающегося с зарезервированного слова except в Delphi.

Если вы передадите функции StrToInt допустимую строку, и при этом исключение не возникнет, будет выполнен только тот код, который находится в блоке try. Код в блоке исключения выполняется только в том случае, если оператор, находящийся внутри этого блока, сгенерирует исключение.

procedure TForm1.Button1Click(Sender: TObject);
var
  x: Integer;
begin
  try
    x := StrToInt(Edit1.Text);
  except
    ShowMessage('Ошибка преобразования');
  end;
end;

Обработка специфических исключений в Delphi

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

ex1-1

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

Одно из них, EConvertError, может быть сгенерировано в том случае, если значение одного из компонентов TEdit невозможно преобразовать к целому типу, а другое, EDivByZero, может быть сгенерировано тогда, когда предпринимается попытка разделить первое число на 0.

procedure TForm1.Button1Click(Sender: TObject);
var
  Num1,Num2: Integer;
begin
  try
    Num1 := StrToInt(Edit1.Text);
    Num2 := StrToInt(Edit2.Text);
    ShowMessage('Результат: ' + IntToStr(Num1 div Num2));
  except
    ShowMessage('Вы не можете делить эти числа');
  end;
end;

Несмотря на то, что вы можете написать обработчик для перехвата всех исключений, вы должны постараться обрабатывать только специфические исключения. Обработать специфическое исключение можно с помощью зарезервированного слова on, с которым связан следующий синтаксис:

on Некоторое-Исключение do Обработка_Исключения;

Конструкцию on-do можно использовать только в рамках обработчика исключений:

try
    оператор (операторы);
except
    on Исключение do Обработка_Исключения;
    on Другое_Исключение do Его_Обработка;

По мере возможности, для обработки различных исключений лучше использовать конструкцию on-do. Например, вы можете обработать исключение EConvertError, выводя сообщение об ошибке, а исключение EDivByZero — уведомляя пользователя о том, что второе число не может быть равно нулю, и автоматически заменяя его единицей.

procedure TForm1.Button1Click(Sender: TObject);
var
  Num1,Num2: Integer;
begin
  try
    Num1 := StrToInt(Edit1.Text);
    Num2 := StrToInt(Edit2.Text);
    ShowMessage('Результат: ' + IntToStr(Num1 div Num2));
  except
    on EConvertError do
      ShowMessage('Одно из чисел неправильно');
    on EDivByZero do
    begin
      ShowMessage('Делитель не может быть равным 0');
      Edit2.Text := '1';
      Edit2.SetFocus;
    end;  // Завершение конструкции on EDivByZero do
  end;
end;

Если конструкцию on-do использовать для обработки специфических исключений, вы должны также написать код для обработки ошибок, о которых вам ничего не будет известно. Чтобы обработать исключения, которые вам не удастся обработать специфическим образом, можно добавить к обработчику исключения часть else.

try
    оператор (операторы);
except
    on Исключение do Его_Обра6отка;
    on Другое_Исключение do Его_Обработка;
else
   Обработка_Всех_Остальных_Исключений;
end;

Генерация исключений

Зарезервированное слово raise используется для генерации исключения. Чтобы сгенерировать исключение в Delphi, используйте зарезервированное слово raise, указывая вслед за ним экземпляр объекта исключения. Экземпляром объекта исключения обычно является вызов конструктора исключения.

Синтаксис генерации исключения обычно выглядит следующим образом:

raise Класс_Исключения.Create('Сообщение_Об_Ошибке');

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

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    Edit2: TEdit;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    function CustomStrToInt(const s:string): Integer;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TForm1 }

procedure TForm1.Button1Click(Sender: TObject);
var
  Num1, Num2: Integer;
begin
  Num1 := CustomStrToInt(Edit1.Text);
  Num2 := CustomStrToInt(Edit2.Text);
  ShowMessage(IntToStr(Num1 div Num2));
end;

function TForm1.CustomStrToInt(const s: string): Integer;
var
  ErrorCode: Integer;
begin
  Val(s, Result, ErrorCode);
  if ErrorCode <> 0 then
  begin
    if s = '' then
      raise EConvertError.Create('Пустая строка не может использоваться')
    else
      raise EConvertError.Create('Привет. Вы не можете конвертировать "'
                                  + s + '" в целое');
  end;
end;

end.

Использование объекта исключения

Конструкция on-do позволяет получать на время объект исключения с помощью следующего синтаксиса

on Идентификатор: Исключение do Его_Обработка;

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

procedure TForm1.Button1Click(Sender: TObject);
var
  x,y: Integer;
begin
  x := 20;
  y := 0;
  try
    Caption := IntToStr(x div y);
  except
    on E: EDivByZero do
      ShowMessage('Exception: ' + E.ClassType.ClassName + #13 +
                  'Exception.Message: ' + E.Message);
  end;
end;

Создание специальных исключений в Delphi

Создать специальное исключение несложно, и этот процесс ничем не отличается от создания специального класса. Специальные исключения должны порождаться от класса Exception или другого потомка этого класса. Имена классов исключений должны начинаться с заглавной буквы Е.

type
    EMyException = class(Exception);

В листинге ниже показана генерация и перехват специального исключения в Delphi.

e1-2

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  ENoUpperCaseLetters = class(Exception);

  TForm1 = class(TForm)
    Label1: TLabel;
    Edit1: TEdit;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    function CountUpperCase(const s: string): Integer;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TForm1 }

procedure TForm1.Button1Click(Sender: TObject);
var
  Cnt: Integer;
begin
  try
    Cnt := CountUpperCase(Edit1.Text);
    Caption := IntToStr(Cnt) + ' заглавных букв';
  except
    on E: ENoUpperCaseLetters do
      Caption := E.Message;
  end;
end;

function TForm1.CountUpperCase(const s: string): Integer;
var
  ch: Char;
begin
  Result := 0;
  for ch in s do
    if ch in ['A'..'Z'] then
      Inc(Result);
  {Вызываем исключение, если отсутствуют буквы в верхнем регистре}
  if Result = 0 then
    raise ENoUpperCaseLetters.Create('Нет заглваных букв');
end;

end.

Защита распределения ресурсов

Зарезервированное слово try позволяет построить два различных блока: блок обработчика, исключений и блок защиты ресурсов. Блок обработчика исключений создается с помощью зарезервированного слова except, а блок защиты ресурсов— с помощью зарезервированного слова finally. Синтаксическая структура блока защиты ресурсов в Delphi выглядит следующим образом:

try
...
finally
...
end;

Блоки обработки исключений и защиты ресурсов используются по-разному и работают также по-разному. Операторы обработчика исключений выполняются только в том случае, если операторы в блоке try сгенерировали исключение, а операторы в блоке finally выполняются всегда, даже если операторы в блоке try не сгенерировали никакого исключения. Если в блоке try возникнет исключение, управление будет передано блоку finally, после чего будет выполнен код очистки. Если в блоке try исключения не возникнут, операторы в блоке finally будут выполняться после операторов в блоке try.

Подходящим способом использования блока защиты ресурсов является распределение или, с другой стороны, затребование ресурса перед блоком try. После того как вы затребуете ресурс, поместите операторы, использующие ресурс, внутрь блока try. Когда работа с ресурсом будет завершена, вы должны будете освободить его. Операторы, освобождающие ресурс, должны быть написаны в блоке finally.

{ Запрос ресурса }
try
    { Использование полученного ресурса }
finally
    { Освобождение ресурса }
end;

Блок защиты ресурса часто используется для того, чтобы обеспечить надлежащее освобождение динамически созданных объектов. Например, динамическое создание модальной формы необходимо всегда защищать с помощью блока try-finally.

procedure TForm1.Button1Click(Sender: TObject);
var
  NewForm: TForm;
begin
  NewForm := TForm.Create(Self);
  try
    NewForm.ShowModal;
  finally
    NewForm.Free;
  end;
end;

В листинге ниже показан более короткий способ динамического создания формы, защищенной блоком try-finally.

procedure TForm1.Button1Click(Sender: TObject);
begin
  with TForm.Create(Self) do
  try
    ShowModal;
  finally
    Free;
  end;
end;

Другое отличие между блоками обработки исключений и блоками обработки ресурсов заключается в том, что блок обработки ресурсов не обрабатывает исключения. Таким образом, если исключение возникнет, оно будет передано первому доступному обработчику исключений. Например, если вы выполните следующий код, то исключение EDivByZero приведет к тому, что обработчик исключений, используемый по умолчанию, выведет на экран монитора сообщение об ошибке, информирующее пользователя о возникшем исключении.

procedure TForm1.Button1Click(Sender: TObject);
begin
  with TForm.Create(Self) do
  try
    {Вызов исключения EDivByZero, поскольку значение Tag равно 0}
    Caption := IntToStr(Top div Tag);
    ShowModal;
  finally
    Free;
  end;
end;

Если вы хотите обработать исключение EDivByZero (или любое другое исключение) внутри блока защиты ресурсов, вы должны написать вложенный блок обработчика исключений.

Следующий листинг показывает, каким должен быть вложенный обработчик исключений внутри блока защиты ресурсов. Вы можете также вкладывать блоки защиты ресурсов в другие блоки защиты ресурсов или блоки обработки исключений.

procedure TForm1.Button1Click(Sender: TObject);
begin
  with TForm.Create(Self) do
  try
    try
      Caption := IntToStr(Top div Tag);
    except
      on EDivByZero do Caption := 'Tag = 0';
    end;
    ShowModal;
  finally
    Free;
  end;
end;

ИЗМЕНЕНИЕ ОБРАБОТЧИКА ИСКЛЮЧЕНИЙ, ИСПОЛЬЗУЕМОГО ПО УМОЛЧАНИЮ

Глобальный объект Application отвечает за обработку исключений, не обрабатываемых блоком обработки исключений, который может находиться где-то в приложении. Чтобы изменить обработчик исключений, используемый по умолчанию, мы можем использовать компонент TApplicationEvents относящийся к категории Additional (Дополнительные).

Компонент TApplicationEvents предлагает событие OnException, которое генерируется всякий раз, когда возникает необработанное исключение.

Событие OnException может быть обработано с помощью процедуры типа TExceptionEvent. Процедура, обрабатывающая событие OnException, принимает два параметра: объект Sender и объект Exception.

procedure TMainForm.AppEventsException[Sender: TObject; E: Exception);
begin

end;

В рамках обработчика события OnException вы можете написать код, который будет обрабатывать исключения иным способом, нежели обработчик, используемый по умолчанию, или же оставить обработчик события пустым. Если вы не хотите, чтобы при возникновении исключения что-либо происходило, оставьте обработчик события пустым. В данном случае потребуется написать внутри блока обработчика события только какой-нибудь комментарий, чтобы редактор Code Editor не удалил код обработчика автоматически.

Обработчик события OnException может использоваться и более конструктивным образом. Например, вы можете написать код, который будет регистрировать все исключения и записывать их в текстовый файл для последующего просмотра.

В листинге ниже показано, как производится регистрация исключений внутри обработчика события OnException.

procedure TForm1.ApplicationEvents1Exception(Sender: TObject; E: Exception);
var
  Log: TextFile;
  LogFilePath: string;
begin
  LogFilePath := 'c:\exceptions.log';
  AssignFile(Log, LogFilePath);
  try
    if not FileExists(LogFilePath) then
      Rewrite(Log)
    else
      Append(Log);
    Writeln(Log, E.ClassType.ClassName, ' исключение с сообщением "', E.Message, '" ');
  finally
    CloseFile(Log);
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Caption := IntToStr(Top div Tag);
end;

Вы можете также модифицировать обработчик исключений, используемый по умолчанию, вручную (без применения компонента TApplicationEvents). Для этого нужно создать метод, который будет принимать те же параметры, что и событие OnException и назначить этот метод событию OnException в глобальном объекте Application.

procedure TForm1.Button1Click(Sender: TObject);
begin
  Caption := IntToStr(Top div Tag);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Application.OnException := MyHandler;
end;

procedure TForm1.MyHandler(Sender: TObject; E: Exception);
begin
  MessageDlg('Do you like the "' + E.Message + '" exception?',
              mtConfirmation, mbYesNo, 0);
end;

Источник: Иван Хладни - Внутренний мир Borland Delphi 2006.