Delphi получить код ошибки

>
Как узнать код ошибки try except

  • Подписаться на тему
  • Сообщить другу
  • Скачать/распечатать тему



Сообщ.
#1

,
27.10.09, 12:43

    Senior Member

    ****

    Рейтинг (т): 3

    Подскажите, можно ли как нибудь отловить код ошибки?
    Объясняю на примере:
    Вот отлавливает деление на ноль, к примеру:

    ExpandedWrap disabled

      //readln(A);  =0;

      try

      Z:=Z/A;

      except on E: exception do

      ShowMessage(e.messages);

      end;

    Ну это конечно ясно, что он напишет ‘Division by zero’, но это во-первых по английски, а во вторых, если мы напишем сразу сообщение по русски, т.е. так:

    ExpandedWrap disabled

      //readln(A);  =0;

      try

      Z:=Z/A;

      except

      ShowMessage(‘Деление на ноль!’);

      end;

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

    ExpandedWrap disabled

      //readln(A);  =0;

      try

      Z:=Z/A;

      except on E: exception do

      if (e.messages=’Division by zero’) then ShowMessage(‘Деление на ноль’) else

      ShowMessage(e.messages);

      end;

    Почему вариант тупой, да потому что, перед тем как вставить текст сообщения об ошибке надо:
    1. Сделать эту ошибку
    2. вывести её на экран
    3. скопировать её текст «символ в символ»
    4. потом вставить это сообщение в код для сравнения.

    Хочется что-то типа того, что текст ошибки сообщения мог бы заменять какой-нибудь её индетификатор, чтобы можно было типа такого:

    ExpandedWrap disabled

      //readln(A);  =0;

      try

      Z:=Z/A;

      except on E: exception do

      if (e.код_ошибки=123) then ShowMessage(‘Деление на ноль’) else

      ShowMessage(e.messages);

      end;

    где «123» есть индетификатор ошибки, т.е. заменяет сам текст ‘Division by zero’. Да, и у e нет параметра на английском, что я написал на русском код_ошибки.

    Ууууу…. Пока писал — сам устал. :)
    Если кто поймёт меня, помогите разобраться.


    CodeMonkey



    Сообщ.
    #2

    ,
    27.10.09, 12:52

      ExpandedWrap disabled

        on E: EDivByZero do

      Подробно и в деталях.


      Continental



      Сообщ.
      #3

      ,
      27.10.09, 14:21

        Senior Member

        ****

        Рейтинг (т): 3

        Спасибо за ссылку.
        А вот в каком модуле находятся классы исключений при работе с ibx компонентами.
        Т.е. мне надо вот к примеру такой класс исключения:

        ExpandedWrap disabled

          except

                    on E: EIBInterBaseError do begin

                          sShowMessage(e.ClassName,e.Message);

                          exit;

                          end;

        а он мне пишет: Неизвестная переменная EIBInterBaseError

        Вообще класс такой есть, он даже его показывает, если я делаю так:

        ExpandedWrap disabled

                  try

                  Connected:=true;

                  except

                    on E: exception do

                          sShowMessage(e.ClassName,e.Message);

                    end;

        Вот e.ClassName как раз и есть EIBInterBaseError, только я не могу класс коде указать.

        Сообщение отредактировано: Continental — 27.10.09, 14:48


        mitrich



        Сообщ.
        #4

        ,
        27.10.09, 14:57

          Senior Member

          ****

          Рейтинг (т): 99

          Хелп говорит, что EIBInterBaseError находится в юните IB


          CodeMonkey



          Сообщ.
          #5

          ,
          27.10.09, 14:59

            Цитата Continental @ 27.10.09, 14:21

            А вот в каком модуле находятся классы исключений при работе с ibx компонентами.

            Пуск/Поиск. *.pas с текстом EIBInterBaseError.
            Для ускорения поиска лучше ткнуть в папку Delphi.

            Цитата Continental @ 27.10.09, 14:21

            Вообще класс такой есть, он даже его показывает, если я делаю так

            Вместо

            ExpandedWrap disabled

              except

                on E: SomeClass do

                …

            можно (но не рекомендуется) использовать:

            ExpandedWrap disabled

              except

                on E: Exception do

                  if E.ClassName = ‘SomeClass’ then

                    …

                  else

                    raise;


            Continental



            Сообщ.
            #6

            ,
            27.10.09, 15:51

              Senior Member

              ****

              Рейтинг (т): 3

              ММММ…. всё равно прочитав всё, не могу реализовать то что я хочу.
              Вот к примеру:
              1. я указываю несуществующий путь к БД в TIBDataBase. Делаю подключение и try показывает ошибку класса EIBDataBaseError, при этом вот такой текст ошибки: ‘IO Error ….и т.д.’.
              2. я указываю существующий путь к БД в TIBDataBase. Указываю неверный логин или верный логин, но неверный пароль и мне ошибка класса EIBDataBaseError, но текст сообщения уже другой! Текст такой: ‘Your user name …. are not defined. …’.

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

              ExpandedWrap disabled

                        try

                        IBDataBase.Connected:=true;

                        except

                          on E : что здесь должно быть, чтобы определить 1-й случай? do begin

                             sShowMessage(‘несуществующий путь бд!’);

                             exit;

                             end;

                          on E : что здесь должно быть, чтобы определить 2-й случай? do begin

                             sShowMessage(‘неверный пользователь или пароль’);

                             exit;

                             end;

                          on E : Exception do begin //люая другая ошибка

                             sShowMessage(e.ClassName,E.Message+’ совершенно другая ошибка’);

                             exit;

                             end;

                          end;

              Помогите разобраться пожалуйста, я очень хочу сделать так, но не знаю.


              CodeMonkey



              Сообщ.
              #7

              ,
              27.10.09, 16:11

                Цитата Continental @ 27.10.09, 15:51

                Так вот я хочу сказать, что в двух случаях ошибки совсем разные, а вот класс исключения один и тот же.

                Если у вашего EIBDataBaseError нет какого-нибудь IBErrorCode — то это криво спроектированные классы Interbase.

                Цитата Continental @ 27.10.09, 15:51

                Так я не могу понять, как же мне отловить первую ошибку, или вторую вот с помощью такой конструкции:

                Если никакого поля с кодом нет, то по-хорошему — никак.

                (Т.е. вы можете, конечно, попробовать анализировать сообщение, но ведь его могут локализовать).

                Добавлено 27.10.09, 16:13
                P.P.S.
                Если же поле кода есть, то в общем случае как-то так:

                ExpandedWrap disabled

                  try

                    IBDataBase.Connected:=true;

                  except

                    on E: EIBDataBaseError do

                      if E.ErrorCode = XXX then

                      begin

                        sShowMessage(‘несуществующий путь бд!’);

                        exit;

                      end

                      else

                      if E.ErrorCode = YYY then

                      begin

                        sShowMessage(‘неверный пользователь или пароль’);

                        exit;

                      end

                      else

                        raise;

                  end;

                Добавлено 27.10.09, 16:15
                Или:

                ExpandedWrap disabled

                  try

                    IBDataBase.Connected:=true;

                  except

                    on E: Exception do

                    begin

                      if E is EIBDataBaseError then

                      begin

                        if EIBDataBaseError(E).ErrorCode = XXX then

                        begin

                          sShowMessage(‘несуществующий путь бд!’);

                          exit;

                        end

                        else

                        if EIBDataBaseError(E).ErrorCode = YYY then

                        begin

                          sShowMessage(‘неверный пользователь или пароль’);

                          exit;

                        end;

                      end;

                      ShowMessage(e.ClassName,E.Message+’ совершенно другая ошибка’);

                    end;

                  end;

                Добавлено 27.10.09, 16:17
                P.P.P.S. Но в общем и целом, если вам это нужно только для показа сообщения об ошибке «на русском», то лучше бы вам локализовать константы вместо того, чтобы ставить кучу обработчиков по всей программе.


                mitrich



                Сообщ.
                #8

                ,
                27.10.09, 16:46

                  Senior Member

                  ****

                  Рейтинг (т): 99

                  Цитата CodeMonkey @ 27.10.09, 16:11

                  Если у вашего EIBDataBaseError нет какого-нибудь IBErrorCode

                  Именно так это свойство и называется, что нетрудно узнать из хелпа (который надеется на длительное и плодотворное сотрудничество с Continental :) )
                  Значения этих кодов прописаны в файле IBErrorCodes.pas (ищи его где-нибудь в C:Program FilesBorlandDelphi7SourceVclIBErrorCodes.pas, к примеру)


                  Continental



                  Сообщ.
                  #9

                  ,
                  27.10.09, 16:47

                    Senior Member

                    ****

                    Рейтинг (т): 3

                    С облегчением … :)

                    ExpandedWrap disabled

                              try

                              Connected:=true;

                              except

                                on E : EIBInterBaseError do begin

                                   sShowMessage(IntToStr(e.IBErrorCode),E.Message);

                                   exit;

                                   end;

                    Спасибо всем, что открыли глаза.

                    Добавлено 27.10.09, 17:01
                    О! вообще круто! CodeMonkey, спасибо что подсказали вот это

                    ExpandedWrap disabled

                      except

                        on E: Exception do

                        begin

                          if E is EIBDataBaseError then

                          begin

                            if EIBDataBaseError(E).ErrorCode = XXX then

                    Я теперь это заделал в applicationevents. Теперь у меня будет и лог вестись и сообщения об ошибках выводится! Е-Е-Е!


                    CodeMonkey



                    Сообщ.
                    #10

                    ,
                    27.10.09, 18:18

                      Для лога ошибок имеет смысл использовать хук на исключения. См. JCL, EurekaLog, madExcept. Помимо сообщения этот способ позволит получить стек вызовов и состояние процессора в момент ошибки. С этой информацией зачастую продиагностировать ошибку можно даже не запуская отладчик.

                      0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)

                      0 пользователей:

                      • Предыдущая тема
                      • Delphi: Общие вопросы
                      • Следующая тема

                      [ Script execution time: 0,0475 ]   [ 16 queries used ]   [ Generated: 3.06.23, 23:55 GMT ]  

                      Обработка исключительных ситуаций в Delphi

                      Содержание

                        Обзор
                        Структурная обработка исключительных ситуаций
                        Модель исключительных ситуаций в Delphi
                        Синтаксис обработки исключительных ситуаций
                        Примеры обработки исключительных ситуаций
                        Вызов исключительной ситуации
                        Доступ к экземпляру объекта exception
                        Предопределенные обработчики исключительных ситуаций
                        Исключения, возникающие при работе с базами данных
                        Заключение

                      Обзор

                      С целью поддержки структурной обработки исключительных ситуаций (exception) в Delphi введены новые расширения языка Pascal. В данной статье будет дано описание того, что из себя представляет такая обработка, почему она полезна, будут приведены соответствующий синтаксис Object Pascal и примеры использования исключительных ситуаций в Delphi.

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

                      Структурная обработка исключительных ситуаций — это система, позволяющая программисту при возникновении ошибки (исключительной ситуации) связаться с кодом программы, подготовленным для обработки такой ошибки. Это выполняется с помощью языковых конструкций, которые как бы «охраняют» фрагмент кода программы и определяют обработчики ошибок, которые будут вызываться, если что-то пойдет не так в «охраняемом» участке кода. В данном случае понятие исключительной ситуации относится к языку и не нужно его путать с системными исключительными ситуациями (hardware exceptions), такими как General Protection Fault. Эти исключительные ситуации обычно используют прерывания и особые состояния «железа» для обработки критичной системной ошибки; исключительные ситуации в Delphi же независимы от «железа», не используют прерываний и используются для обработки ошибочных состояний, с которыми подпрограмма не готова иметь дело. Системные исключительные ситуации, конечно, могут быть перехвачены и преобразованы в языковые исключительные ситуации, но это только одно из применений языковых исключительных ситуаций.

                      При традиционной обработке ошибок, ошибки, обнаруженные в процедуре обычно передаются наружу (в вызывавшую процедуру) в виде возвращаемого значения функции, параметров или глобальных переменных (флажков). Каждая вызывающая процедура должна проверять результат вызова на наличие ошибки и выполнять соответствующие действия. Часто, это просто выход еще выше, в более верхнюю вызывающую процедуру и т.д. : функция A вызывает B, B вызывает C, C обнаруживает ошибку и возвращает код ошибки в B, B проверяет возвращаемый код, видит, что возникла ошибка и возвращает код ошибки в A, A проверяет возвращаемый код и выдает сообщение об ошибке либо решает сделать что-нибудь еще, раз первая попытка не удалась.

                      Такая «пожарная бригада» для обработки ошибок трудоемка, требует написания большого количества кода в котором можно легко ошибиться и который трудно отлаживать.

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

                      Структурная обработка исключительной ситуации замещает ручную обработку ошибок автоматической, сгенерированной компилятором системой уведомления. В приведенном выше примере, процедура A установила бы «охрану» со связанным обработчиком ошибки на фрагмент кода, в котором вызывается B. B просто вызывает C. Когда C обнаруживает ошибку, то создает (raise) исключительную ситуацию. Специальный код, сгенерированный компилятором и встроенный в Run-Time Library (RTL) начинает поиск обработчика данной исключительной ситуации. При поиске «защищенного» участка кода используется информация, сохраненная в стеке. В процедурах C и B нет такого участка, а в A — есть. Если один из обработчиков ошибок, которые используются в A, подходит по типу для возникшей в C исключительной ситуации, то программа переходит на его выполнение. При этом, область стека, используемая в B и C, очищается; выполнение этих процедур прекращается.

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

                      Без проверки возвращаемого кода после каждого вызова подпрограммы, код программы должен быть более простым, а скомпилированный код — более быстрым. При наличии исключительных ситуаций подпрограмма B не должна содержать дополнительный код для проверки возвращаемого результата и передачи его в A. B ничего не должна делать для передачи исключительной ситуации, возникшей в C, в процедуру A — встроенная система обработки исключительных ситуаций делает всю работу.

                      Данная система называется структурной, поскольку обработка ошибок определяется областью «защищенного» кода; такие области могут быть вложенными. Выполнение программы не может перейти на произвольный участок кода; выполнение программы может перейти только на обработчик исключительной ситуации активной программы.

                      Модель исключительных ситуаций в Delphi

                      Модель исключительных ситуаций в Object Pascal является невозобновляемой(non-resumable). При возникновении исключительной ситуации Вы уже не сможете вернуться в точку, где она возникла, для продолжения выполнения программы (это позволяет сделать возобновляемая(resumable) модель). Невозобновляемые исключительные ситуации разрушают стек, поскольку они сканируют его в поисках обработчика; в возобновляемой модели необходимо сохранять стек, состояние регистров процессора в точке возникновения ошибки и выполнять поиск обработчика и его выполнение в отдельном стеке. Возобновляемую систему обработки исключительных ситуаций гораздо труднее создать и применять, нежели невозобновляемую.

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

                      Теперь, когда мы рассмотрели, что такое исключительные ситуации, давайте дадим ясную картину, как они применяются. Новое ключевое слово, добавленное в язык Object Pascal — try. Оно используется для обозначения первой части защищенного участка кода. Существует два типа защищенных участков:

                      • try..except
                      • try..finally

                      Первый тип используется для обработки исключительных ситуаций. Его синтаксис:

                      try
                        Statement 1;
                        Statement 2;
                        ...
                      except
                        on Exception1 do Statement;
                        on Exception2 do Statement;
                        ...
                      else
                        Statements; {default exception-handler}
                      end;

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

                      try
                        Statement1;
                        Statement2;
                        ...
                      finally
                        Statements;  { These statements always execute }
                      end;

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

                      Ниже приведены процедуры A,B и C, обсуждавшиеся ранее, воплощенные в новом синтаксисе Object Pascal:

                      type
                        ESampleError = class(Exception);
                      var
                        ErrorCondition: Boolean;
                      procedure C;
                      begin
                        writeln('Enter C');
                        if (ErrorCondition) then
                        begin
                          writeln('Raising exception in C');
                          raise ESampleError.Create('Error!');
                        end;
                        writeln('Exit C');
                      end;
                      procedure B;
                      begin
                        writeln('enter B');
                        C;
                        writeln('exit B');
                      end;
                      procedure A;
                      begin
                        writeln('Enter A');
                        try
                          writeln('Enter A''s try block');
                          B;
                          writeln('After B call');
                        except
                          on ESampleError do
                            writeln('Inside A''s ESampleError handler');
                          on ESomethingElse do
                            writeln('Inside A''s ESomethingElse handler');
                        end;
                        writeln('Exit A');
                      end;
                      begin
                        writeln('begin main');
                        ErrorCondition := True;
                        A;
                        writeln('end main');
                      end. 

                      При ErrorCondition = True программа выдаст:

                      begin main
                      Enter A
                      Enter A's try block
                      enter B
                      Enter C
                      Raising exception in C
                      Inside A's ESampleError handler
                      Exit A
                      end main

                      Возможно вас удивила декларация типа ‘ESampleError =class’ вместо ‘=object’; это еще одно новое расширение языка. Delphi вводит новую модель объектов, доступную через декларацию типа ‘=class’. Описание новой объектной модели дается в других уроках. Здесь же достаточно сказать, что исключительные ситуации (exceptions) являются классами, частью новой объектной модели.

                      Процедура C проверяет наличие ошибки (в нашем случае это значение глобальной переменной) и, если она есть (а это так), C вызывает(raise) исключительную ситуацию класса ESampleError.

                      Процедура A помещает часть кода в блок try..except. Первая часть этого блока содержит часть кода, аналогично конструкции begin..end. Эта часть кода завершается ключевым словом except, далее следует один или более обработчиков исключительных ситуаций on xxxx do yyyy, далее может быть включен необязательный блок else, вся конструкция заканчивается end;. В конструкции, назначающей определенную обработку для конкретной исключительной ситуации (on xxxx do yyyy), после резервного слова on указывается класс исключительной ситуации, а после do следует собственно код обработки данной ошибки. Если возникшая исключительная ситуация подходит по типу к указанному после on, то выполнение программы переходит сюда (на код после do). Исключительная ситуация подходит в том случае, если она того же класса, что указан в on, либо является его потомком. Например, в случае on EFileNotFound обрабатываться будет ситуация, когда файл не найден. А в случае on EFileIO — все ошибки при работе с файлами, в том числе и предыдущая ситуация. В блоке else обрабатываются все ошибки, не обработанные до этого.

                      Приведенные в примере процедуры содержат код (строка с writeln), который отображает путь выполнения программы. Когда C вызывает exception, программа сразу переходит на обработчик ошибок в процедуре A, игнорируя оставшуюся часть кода в процедурах B и C.

                      После того, как найден подходящий обработчик ошибки, поиск оканчивается. После выполнения кода обработчика, программа продолжает выполняться с оператора, стоящего после слова end блока try..except (в примере — writeln(‘Exit A’)).

                      Конструкция try..except подходит, если известно, какой тип ошибок нужно обрабатывать в конкретной ситуации. Но что делать, если требуется выполнить некоторые действия в любом случае, произошла ошибка или нет? Это тот случай, когда понадобится конструкция try..finally.

                      Рассмотрим модифицированную процедуру B:

                      procedure NewB;
                      var
                        P: Pointer;
                      begin
                        writeln('enter B');
                        GetMem(P, 1000);
                        C;
                        FreeMem(P, 1000);
                        writeln('exit B');
                      end;

                      Если C вызывает исключительную ситуацию, то программа уже не возвращается в процедуру B. А что же с теми 1000 байтами памяти, захваченными в B? Строка FreeMem(P,1000) не выполнится и Вы потеряете кусок памяти. Как это исправить? Нужно ненавязчиво включить процедуру B в процесс, например:

                      procedure NewB;
                      var
                        P: Pointer;
                      begin
                        writeln('enter NewB');
                        GetMem(P, 1000);
                        try
                          writeln('enter NewB''s try block');
                          C;
                          writeln('end of NewB''s try block');
                        finally
                          writeln('inside NewB''s finally block');
                          FreeMem(P, 1000);
                        end;
                        writeln('exit NewB');
                      end;

                      Если в A поместить вызов NewB вместо B, то программа выведет сообщения следующим образом:

                      begin main
                      Enter A
                      Enter A's try block
                      enter NewB
                      enter NewB's try block
                      Enter C
                      Raising exception in C
                      inside NewB's finally block
                      Inside A's ESampleError handler
                      Exit A
                      end main

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

                      Почему вызов GetMem не помещен внутрь блока try? Этот вызов может окончиться неудачно и вызвать exception EOutOfMemory. Если это произошло, то FreeMem попытается освободить память, которая не была распределена. Когда мы размещаем GetMem вне защищаемого участка, то предполагаем, что B сможет получить нужное количество памяти, а если нет, то более верхняя процедура получит уведомление EOutOfMemory.

                      А что, если требуется в B распределить 4 области памяти по схеме все-или-ничего? Если первые две попытки удались, а третья провалилась, то как освободить захваченную область память? Можно так:

                      procedure NewB;
                      var
                        p,q,r,s: Pointer;
                      begin
                        writeln('enter B');
                        P := nil;
                        Q := nil;
                        R := nil;
                        S := nil;
                        try
                          writeln('enter B''s try block');
                          GetMem(P, 1000);
                          GetMem(Q, 1000);
                          GetMem(R, 1000);
                          GetMem(S, 1000);
                          C;
                          writeln('end of B''s try block');
                        finally
                          writeln('inside B''s finally block');
                          if P <> nil then FreeMem(P, 1000);
                          if Q <> nil then FreeMem(Q, 1000);
                          if R <> nil then FreeMem(R, 1000);
                          if S <> nil then FreeMem(S, 1000);
                        end;
                        writeln('exit B');
                      end;

                      Установив сперва указатели в NIL, далее можно определить, успешно ли прошел вызов GetMem.

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

                      Вызов исключительной ситуации

                      В процедуре C из примера мы уже могли видеть, как должна поступать программа при обнаружении состояния ошибки — она вызывает исключительную ситуацию:

                      raise ESampleError.Create('Error!');

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

                      Почти все существующие классы исключительных ситуаций являются наследниками базового класса Exception и не содержат новых свойств или методов. Класс Exception имеет несколько конструкторов, какой из них конкретно использовать — зависит от задачи. Описание класса Exception можно найти в on-line Help.

                      Доступ к экземпляру объекта exception

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

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

                      Рассмотрим модифицированную процедуру A в нашем примере:

                      procedure NewA;
                      begin
                        writeln('Enter A');
                        try
                          writeln('Enter A''s try block');
                          B;
                          writeln('After B call');
                        except
                          on E: ESampleError do writeln(E.Message);
                          on ESomethingElse do
                            writeln('Inside A''s ESomethingElse handler');
                        end;
                        writeln('Exit A');
                      end;

                      Здесь все изменения внесены в строку

                          on ESE: ESampleError do writeln(ESE.Message);

                      Пример демонстрирует еще одно новшество в языке Object Pascal — создание локальной переменной. В нашем примере локальной переменной является ESE — это тот самый экземпляр класса ESampleError, который был создан в процедуре C в момент вызова исключительного состояния. Переменная ESE доступна только внутри блока do. Свойство Message объекта ESE содержит сообщение, которое было передано в конструктор Create в процедуре C.

                      Есть еще один способ доступа к экземпляру exception — использовать функцию ExceptionObject:

                      on ESampleError do
                        writeln(ESampleError(ExceptionObject).Message);

                      Предопределенные обработчики исключительных ситуаций

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

                      • Exception — базовый класс-предок всех обработчиков исключительных ситуаций.
                      • EAbort — «скрытое» исключение. Используйте его тогда, когда хотите прервать тот или иной процесс с условием, что пользователь программы не должен видеть сообщения об ошибке. Для повышения удобства использования в модуле SysUtils предусмотрена процедура Abort, определенная, как:
                      • procedure Abort;
                        begin
                          raise EAbort.CreateRes(SOperationAborted) at ReturnAddr;
                        end; 
                      • EComponentError — вызывается в двух ситуациях:
                        1. при попытке регистрации компоненты за пределами процедуры Register;
                        2. когда имя компоненты не уникально или не допустимо.
                      • EConvertError — происходит в случае возникновения ошибки при выполнении функций StrToInt и StrToFloat, когда конвертация строки в соответствующий числовой тип невозможна.
                      • EInOutError — происходит при ошибках ввода/вывода при включенной директиве {$I+}.
                      • EIntError — предок исключений, случающихся при выполнении целочисленных операций.
                        • EDivByZero — вызывается в случае деления на ноль, как результат RunTime Error 200.
                        • EIntOverflow — вызывается при попытке выполнения операций, приводящих к переполнению целых переменных, как результат RunTime Error 215 при включенной директиве {$Q+}.
                        • ERangeError — вызывается при попытке обращения к элементам массива по индексу, выходящему за пределы массива, как результат RunTime Error 201 при включенной директиве {$R+}.
                      • EInvalidCast — происходит при попытке приведения переменных одного класса к другому классу, несовместимому с первым (например, приведение переменной типа TListBox к TMemo).
                      • EInvalidGraphic — вызывается при попытке передачи в LoadFromFile файла, несовместимого графического формата.
                      • EInvalidGraphicOperation — вызывается при попытке выполнения операций, неприменимых для данного графического формата (например, Resize для TIcon).
                      • EInvalidObject — реально нигде не используется, объявлен в Controls.pas.
                      • EInvalidOperation — вызывается при попытке отображения или обращения по Windows-обработчику (handle) контрольного элемента, не имеющего владельца (например, сразу после вызова MyControl:=TListBox.Create(…) происходит обращение к методу Refresh).
                      • EInvalidPointer — происходит при попытке освобождения уже освобожденного или еще неинициализированного указателя, при вызове Dispose(), FreeMem() или деструктора класса.
                      • EListError — вызывается при обращении к элементу наследника TList по индексу, выходящему за пределы допустимых значений (например, объект TStringList содержит только 10 строк, а происходит обращение к одиннадцатому).
                      • EMathError — предок исключений, случающихся при выполнении операций с плавающей точкой.
                        • EInvalidOp — происходит, когда математическому сопроцессору передается ошибочная инструкция. Такое исключение не будет до конца обработано, пока Вы контролируете сопроцессор напрямую из ассемблерного кода.
                        • EOverflow — происходит как результат переполнения операций с плавающей точкой при слишком больших величинах. Соответствует RunTime Error 205.
                        • Underflow — происходит как результат переполнения операций с плавающей точкой при слишком малых величинах. Соответствует RunTime Error 206.
                        • EZeroDivide — вызывается в результате деления на ноль.
                      • EMenuError — вызывается в случае любых ошибок при работе с пунктами меню для компонент TMenu, TMenuItem, TPopupMenu и их наследников.
                      • EOutlineError — вызывается в случае любых ошибок при работе с TOutLine и любыми его наследниками.
                      • EOutOfMemory — происходит в случае вызовов New(), GetMem() или конструкторов классов при невозможности распределения памяти. Соответствует RunTime Error 203.
                      • EOutOfResources — происходит в том случае, когда невозможно выполнение запроса на выделение или заполнение тех или иных Windows ресурсов (например таких, как обработчики — handles).
                      • EParserError — вызывается когда Delphi не может произвести разбор и перевод текста описания формы в двоичный вид (часто происходит в случае исправления текста описания формы вручную в IDE Delphi).
                      • EPrinter — вызывается в случае любых ошибок при работе с принтером.
                      • EProcessorException — предок исключений, вызываемых в случае прерывания процессора- hardware breakpoint. Никогда не включается в DLL, может обрабатываться только в «цельном» приложении.
                        • EBreakpoint — вызывается в случае останова на точке прерывания при отладке в IDE Delphi. Среда Delphi обрабатывает это исключение самостоятельно.
                        • EFault — предок исключений, вызываемых в случае невозможности обработки процессором тех или иных операций.
                          • EGPFault — вызывается, когда происходит «общее нарушение защиты» — General Protection Fault. Соответствует RunTime Error 216.
                          • EInvalidOpCode — вызывается, когда процессор пытается выполнить недопустимые инструкции.
                          • EPageFault — обычно происходит как результат ошибки менеджера памяти Windows, вследствие некоторых ошибок в коде Вашего приложения. После такого исключения рекомендуется перезапустить Windows.
                          • EStackFault — происходит при ошибках работы со стеком, часто вследствие некорректных попыток доступа к стеку из фрагментов кода на ассемблере. Компиляция Ваших программ со включенной проверкой работы со стеком {$S+} помогает отследить такого рода ошибки.
                        • ESingleStep — аналогично EBreakpoint, это исключение происходит при пошаговом выполнении приложения в IDE Delphi, которая сама его и обрабатывает.
                      • EPropertyError — вызывается в случае ошибок в редакторах свойств, встраиваемых в IDE Delphi. Имеет большое значение для написания надежных property editors. Определен в модуле DsgnIntf.pas.
                      • EResNotFound — происходит в случае тех или иных проблем, имеющих место при попытке загрузки ресурсов форм — файлов .DFM в режиме дизайнера. Часто причиной таких исключений бывает нарушение соответствия между определением класса формы и ее описанием на уровне ресурса (например,вследствие изменения порядка следования полей-ссылок на компоненты, вставленные в форму в режиме дизайнера).
                      • EStreamError — предок исключений, вызываемых при работе с потоками.
                        • EFCreateError — происходит в случае ошибок создания потока (например, при некорректном задании файла потока).
                        • EFilerError — вызывается при попытке вторичной регистрации уже зарегистрированного класса (компоненты). Является, также, предком специализированных обработчиков исключений, возникающих при работе с классами компонент.
                          • EClassNotFound — обычно происходит, когда в описании класса формы удалено поле-ссылка на компоненту, вставленную в форму в режиме дизайнера. Вызывается, в отличие от EResNotFound, в RunTime.
                          • EInvalidImage — вызывается при попытке чтения файла, не являющегося ресурсом, или разрушенного файла ресурса, специализированными функциями чтения ресурсов (например, функцией ReadComponent).
                          • EMethodNotFound — аналогично EClassNotFound, только при несоответствии методов, связанных с теми или иными обработчиками событий.
                          • EReadError — происходит в том случае, когда невозможно прочитать значение свойства или другого набора байт из потока (в том числе ресурса).
                        • EFOpenError — вызывается когда тот или иной специфированный поток не может быть открыт (например, когда поток не существует).
                      • EStringListError — происходит при ошибках работы с объектом TStringList (кроме ошибок, обрабатываемых TListError).

                      Исключения, возникающие при работе с базами данных

                      Delphi, обладая прекрасными средствами доступа к данным, основывающимися на интерфейсе IDAPI, реализованной в виде библиотеки Borland Database Engine (BDE), включает ряд обработчиков исключительных ситуаций для регистрации ошибок в компонентах VCL работающим с БД. Дадим краткую характеристику основным из них:

                      • EDatabaseError — наследник Exception ; происходит при ошибках доступа к данным в компонентах-наследниках TDataSet. Объявлено в модуле DB. Ниже приведен пример из Delphi On-line Help, посвященный этому исключению:
                      • repeat {пока не откроем таблицу или не нажмем кнопку  Cancel}
                          try
                            Table1.Active := True; {Пытаемся открыть таблицу}
                            Break; { Если нет ошибки - прерваем цикл}
                          except
                            on EDatabaseError do
                              {Если нажата OK - повторяем попытку открытия  Table1}
                              if MessageDlg('Не могу открыть  Table1', mtError,
                        			[mbOK, mbCancel], 0) <> mrOK
                              then
                                raise;
                          end;
                        until False;
                        • EDBEngineError — наследник EDatabaseError ; вызывается, когда происходят
                          ошибки BDE или на сервере БД. Объявлено в модуле DB:
                        • EDBEngineError = class(EDatabaseError)
                          private
                            FErrors: TList;
                            function GetError(Index: Integer): TDBError;
                            function GetErrorCount: Integer;
                          public
                            constructor Create(ErrorCode: DBIResult);
                            destructor Destroy;
                            property ErrorCount: Integer;
                            property Errors[Index: Integer]: TDBError;
                          end;

                          Особенно важны два свойства класса EDBEngineError : Errors
                          — список всех ошибок, находящихся в стеке ошибок BDE. Индекс первой ошибки
                          0;
                          ErrorCount — количество ошибок в стеке.
                          Объекты, содержащиеся в Errors, имеют тип TDBError. Доступные свойства класса
                          TDBError:
                          ErrorCode — код ошибки, возвращаемый Borland Database Engine;
                          Category — категория ошибки, описанной в ErrorCode;
                          SubCode — ‘субкод’ ошибки из ErrorCode;
                          NativeError — ошибка, возвращаемая сервером БД. Если NativeError 0, то ошибка
                          в ErrorCode не от сервера;
                          Message — сообщение, переданное сервером, если NativeError не равно 0; сообщение
                          BDE — в противном случае.

                      • EDBEditError — наследник Exception ; вызывается, когда данные не совместимы с маской ввода, наложенной на поле. Объявлено в модуле Mask.

                      Заключение

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

                      Назад | Содержание
                      | Вперед


                      Комплексный контроль за качеством кода


                      Оформил: DeeCo

                      Автор: Николай Мазуркин

                      Оглавление

                      1. Введение
                      2. Процедура
                        Assert
                      3. Номера
                        строк или уникальные метки ?
                      4. Категории
                        ошибок
                      5. Тотальный
                        контроль
                      6. Заключение
                      7. Пример
                        модуля с функциями комплексной обработки ошибок
                      8. Пример
                        использования системы комплексного контроля

                      1. Введение

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

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

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

                      2. Процедура ASSERT

                      В Object Pascal введена
                      специализированная процедура Assert, назначение которой — помощь в отладке
                      кода и контроле над выполнением программы. Процедура является аналогом
                      макроса ASSERT, который широко применяется практически во всех программах,
                      написанных с использованием C и C++ и их библиотек. Синтаксис процедуры
                      Assert (макрос имеет похожий синтаксис) описан ниже.

                      procedure Assert(Condition: Boolean; [Msg: string]);

                      Процедура проверяет логическое утверждение, передаваемое первым
                      аргументом и, если это утверждение ложно, то процедура выводит на экран
                      диагностическое сообщение с номером строки и именем модуля, где расположен
                      вызов этой процедуры, и сообщение пользователя, которое опционально
                      передается вторым аргументом. В некоторых системах разработки, например
                      (MSVC+MFC), макрос Assert принудительно завершает выполнение программы
                      после выдачи соответствующего диагностического сообщения. В других
                      системах (например Delphi) стандартная процедура ограничивается лишь
                      выдачей диагностического сообщения.

                      Действие стандартной процедуры Assert (как и соответствующего макроса)
                      зависит от режима компиляции проекта. Обычно, в режиме отладки процедура
                      действует как описано выше, в других же режимах, вызов данной процедуры и
                      проверка условия игнорируются и не компилируются в проект. Считается, что
                      исключение проверки в готовой программе позволяет повысить
                      производительность и уменьшить размер программы.

                      В языках С и С++ отладочный режим компиляции задается определением
                      (#define) соответствующей константы, обычно это _DEBUG. В Object Pascal
                      отладочный режим включается специальной опцией компилятора. Кроме того, в
                      С и С++ макрос ASSERT — это обычный макрос, ничем не отличающийся от
                      множества других макросов. Макрос использует переменную компилятора
                      __LINE__, что позволяет ему определить номер строки, в которой произошло
                      нарушение проверки. В Object Pascal такой переменной нет, и за реализацию
                      процедуры Assert полностью отвечает компилятор, что позволяет говорить о
                      процедуре Assert, как об особенности компилятора и языка Object Pascal, а
                      не как об обычной процедуре в составе библиотеки VCL.

                      Процедура Assert обычно применяется в следующих случаях:

                      • в начале процедуры или функции для проверки правильности переданных
                        аргументов;
                      • в начале процедуры или функции для проверки правильности внутренних
                        переменных;
                      • в конце работы алгоритма для проверки правильности работы алгоритма;
                      • для проверки правильности выполнения «надежных» функций, то есть тех
                        функций, которые всегда должны выполняться успешно всегда, и их
                        невыполнение рассматривается как фатальная ошибка программы. Хороший
                        пример — функция CloseHandle вызываемая с верным дескриптором.
                        Практически, можно не сомневаться в правильности выполнения этой
                        функции, однако результат ее выполнения все-таки можно и нужно
                        проверить. Подробнее об этом в главе «Категории
                        ошибок».

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

                      procedure AddElement(Elem: TObject; Index: Integer);
                      begin
                        // Ссылка на объект не должна быть пустой
                        Assert(Elem <> nil, 'Пустая ссылка.');
                        // Индекс должен быть в пределах нормы
                        Assert((0 <= Index) and (Index < FCount), 'Неверный индекс');
                        // Что-то делаем
                        ...
                          // Проверяем результат алгоритма
                        Assert(Result = 0, 'Ошибка в алгоритме вставки элемента');
                      end;

                      Первая процедура проверяет, является ли переданная ссылка на объект
                      непустой. Вторая процедура проверяет индекс добавляемого элемента на
                      принадлежность к ограниченному диапазону. Третья процедура проверяет
                      правильность выполнения алгоритма. Ясно, что при невыполнении хотя бы
                      одного из этих условий, необходимо считать, что данная процедура
                      выполнилась неправильно, и дальнейшее правильное выполнение всей программы
                      не представляется возможным. Ясно также, что, так как данную процедуру
                      (процедуру AddElement) вызываем только мы (наша программа), такое событие
                      никогда не должно происходить, в предположении, что алгоритм правилен и
                      аргументы, передаваемые в функцию, верные. Однако, как известно: «Человек
                      полагает, а Бог располагает», и при невнимательном программировании в
                      большом проекте такой тип ошибок становиться основным.

                      Без применения этих проверок, программа бы выдала малоинформативное
                      сообщение, например об исключении определенного типа, и конечно без
                      указания места, где это исключение произошло. Поэтому, в большом проекте,
                      интенсивное применение процедуры Assert позволяет быстро локализовать,
                      идентифицировать и устранить возникшую ошибку в работе алгоритма.

                      В настоящее время (в пятой версии Delphi и несколько более ранних
                      версиях), процедура Assert генерирует на месте своего вызова исключение
                      типа EAssertionFailed и дальнейшее следование этого исключения
                      осуществляется обычным образом — вверх по стеку процедур до ближайшего
                      обработчика исключений. Исключения от процедуры Assert обрабатываются
                      таким же образом, как и все другие — объект TApplication ловит все
                      исключения и показывает их тип и их сообщения на экране. Однако такой
                      подход трудно считать логичным. Исключения от процедуры Assert коренным
                      образом отличаются от остальных исключений. Исключения от процедуры Assert
                      свидетельствуют о серьезных ошибках в логике работы программы и требуют
                      особого внимания, вплоть до принудительного завершения программы с выводом
                      особого диагностического сообщения.

                      Реализация процедуры Assert в Object Pascal полностью возложена на
                      компилятор (вернее на «приближенный ко двору» модуль system.pas), что
                      несколько затрудняет изменение логики работы. Возможны три основных
                      варианта внесения изменений в логику работы.

                      1. Установка обработчика события TApplication.OnException и обработка в
                        нем исключения с типом EAssertionFailed. Данный способ является наиболее
                        простым, и менее гибким. Подходит, если все что необходимо — это вывести
                        на экран особое сообщение и принудительно завершить программу.
                      2. Прямая установка обработчика процедуры Assert, путем присвоения
                        адреса своей процедуры системной переменной AssertErrorProc. Область
                        применения практически та же самая, что и в предыдущем случае. Однако, в
                        этом случае, возможны и более сложные манипуляции. Например, можно
                        написать обработчик, который не генерирует исключение, а сразу выводит
                        сообщение и принудительно завершает программу. Пример такого обработчика
                        приведен ниже.
                        procedure AssertHandlerMSG(const Msg, Module: string; Line: Integer;
                          ErrorAddr: Pointer);
                        begin
                          // Выводим сообщение
                          MessageBox(0,
                            PChar(Format('Error "%s" at address 0x%8.8x in file %s, line %d.',
                            [Msg, Integer(ErrorAddr), Module, Line])),
                            'Error',
                            MB_OK
                            );
                          // Выходим из программы
                          ExitProcess(1);
                        end;
                        ...
                        // Настройка ссылки на глобальный обработчик процедуры Assert
                        AssertErrorProc := @AssertHandlerMSG;
                      3. Написание процедуры Assert заново. Самый гибкий и удобный вариант.
                        Например, если вас не устраивает стандартный синтаксис процедуры Assert,
                        и вы хотите передать в процедуру дополнительные параметры (например, тип
                        ошибки, тип генерируемого исключения, код, и т.п.). При этом возникает
                        два особых момента связанных с тем фактом, что за процедуру Assert
                        отвечает компилятор. Во-первых, вы не сможете управлять включением и
                        включением вызовов процедуры Assert в программе через стандартные опции.
                        Процедура Assert будет выполняться всегда. Единственное, что вы можете
                        сделать — это сразу выйти из процедуры, если режим работы программы не
                        отладочный. Во-вторых, в самой процедуре невозможно узнать номер строки,
                        в которой произошел вызов процедуры. Тем не менее, обе этих трудности
                        преодолимы.

                      Необходимость включения и выключения вызовов
                      процедуры Assert, связанных с режимом работы программы является само собой
                      подразумевающейся. Считается, что выключение вызовов Assert в отлаженной
                      программе позволяет уменьшить размер программы и увеличить скорость ее
                      работы. Однако, экономия на размере и увеличение быстродействия являются
                      весьма малыми (если вы не включаете процедуру Assert в тело каждого
                      цикла). Происходящее на этом фоне, молчаливое съедание ошибок в программе
                      ставит под сомнение необходимость отключать вызовы процедуры Assert в
                      «готовой» программе. То, что вы не нашли ошибок при разработке и отладке
                      программы вовсе не означает, что там их нет. Ошибки могут появиться у
                      пользователя программы, например, в условиях, в которых программа не
                      тестировалась. Как вы о них узнаете, если отключите вызовы Assert?
                      Конечно, лукавое отключение предупреждений может на время сохранить вашу
                      репутацию, и пользователь может и не узнать о тех ужасах, которые
                      происходят в недрах вашей программы. А внезапный безымянный сбой вашей
                      программы вы можете списать на особенности работы Windows. Однако качества
                      вашей программе это не прибавит. Честная регистрация всех ошибок намного
                      улучшит ваши программы.

                      Таким образом, первая трудность решена — ее просто не нужно решать.
                      Работайте честно, всегда оставляйте вызовы функций Assert в программе, и
                      они помогут диагностировать ошибки. Решение второй проблемы — указание
                      номеров строк в сообщениях описано в следующей главе. Пример же простейшей
                      альтернативной процедуры Assert приведен ниже. Функция называется
                      AssertMsg и принимает те же параметры, что и стандартная процедура.
                      Конечно, список этих параметров можно расширить, так же, как и изменить
                      логику работы. Данная же процедура генерирует исключение по адресу вызова
                      этой процедуры, а глобальный обработчик исключений выводит сообщение и
                      завершает программу при получении исключения с определенном типом
                      EFatalExcept.

                      procedure AssertMsg(Condition: Boolean; const Mark: string);
                      var
                        ExceptAddr: Pointer;
                      begin
                        // Если условие выполняется, то выходим
                        if Condition then
                          Exit;
                        // Получаем адрес вызвавшей нас команды Call
                        asm
                          mov EAX, [EBP+$04]
                          mov ExceptAddr, EAX
                        end;
                        // Возбуждаем исключение в месте вызова этой функции
                        raise EFatalExcept.Create('Неожиданная ошибка. ' + #13#13+Mark)at
                          ExceptAddr;
                      end;

                      3. Номера строк или уникальные метки ?

                      В
                      большинстве систем разработки, в сообщении, выводимом при активизации
                      процедуры Assert, выводится три параметра: сообщение пользователя, имя
                      исходного модуля и номер строки, в котором находился вызов процедуры. При
                      этом основной упор при поиске ошибки делается на номер строки и имя
                      модуля, а сообщение пользователя является малоинформативным. Однако, если
                      вы решили оставить вызовы процедуры Assert в готовой распространяемой
                      программе, здесь таится проблема. При разработке программы вы будете
                      выпускать десятки и десятки версий, и естественно, что исходный код будет
                      сильно модифицироваться, а номера линий, на которых располагаются
                      проверочные вызовы, будут постоянно изменяться. И если вдруг ваш
                      пользователь сообщает о том, что в вашей программе сработала проверка на
                      линии N в исходном модуле M, вы должны задаться вопросом, какая версия
                      программы находится у пользователя? Даже если вы сохраните все исходные
                      тексты абсолютно всех версий, такой подход нельзя назвать удобным и ясным,
                      он таит в себе скрытую путаницу и неразбериху, поскольку вам придется
                      отслеживать номера строк в разных версиях программы при просмотре развития
                      программы.

                      Возникает идея — совсем отказаться от номеров строк — они таят в себе
                      хаос, и использовать уникальные сообщения пользователя, то есть уникальные
                      метки. В каждом вызове процедуры Assert передавать абсолютно уникальный,
                      не повторяющийся идентификатор, и выводить его на экран при срабатывании
                      проверки. После того как вам сообщат, что вашей программе активизировалась
                      проверка с идентификатором N, вы можете проследить развитие кода в этом
                      месте во всех версиях программы путем простого поиска идентификатора в
                      исходных текстах. Зачастую даже, вовсе не обязательно распаковывать ту же
                      версию исходных текстов программы, что и версия программы у пользователя.
                      Если код в этом месте давно не менялся, вы можете искать и исправлять
                      ошибку прямо в текущей версии исходных текстов.

                      Что выбрать в качестве уникальных идентификаторов? Можно выбрать любое
                      не повторяющееся число или строку. Например, последовательно передавать
                      числа от нуля и далее с инкрементом на единицу. Или передавать уникальные
                      строки, которые можно придумывать на ходу. Однако, это утомительное
                      занятие — помнить последний набранный номер или строку и не повторяться.
                      Можно написать специальный модуль для редактора в среде Delphi, который
                      будет автоматически вводить идентификаторы. Однако, если вы будете
                      работать над проектом на разных машинах, то возникнет проблема
                      синхронизации счетчиков.

                      Существует прекрасный вариант решения этой проблемы. В среде Delphi
                      нажмите клавиши <CTRL>+<SHIFT>+<G> и вы получите строку
                      похожую на эту: [‘{19619100-22B0-11D4-ACD0-009027350D25}’]. Это уникальный
                      идентификатор GUID (Global Unique IDentificator), шестнадцатибайтное
                      уникальное значение, переведенное в строковый вид с разграничителями и
                      заключенное в квадратные скобки. Такое значение уникально, оно зависит от
                      времени и от номера сетевой карты. Если сетевой карты нет, то номер
                      генерируется программно. По крайней мере, Microsoft утверждает, что такой
                      номер никогда не повторится. Каждый раз, когда вы нажмете соответствующую
                      комбинацию клавиш, система сгенерирует новый уникальный код. Если удалить
                      квадратные скобки по бокам, то получиться отличный уникальный строковый
                      идентификатор, готовый к использованию, при этом вызов процедуры AssertMsg
                      будет выглядеть подобным образом.

                      AssertMsg(FCount > 0, '{19619100-22B0-11D4-ACD0-009027350D25}');

                      В данном случае сообщение пользователя не передается — передается
                      только уникальная метка. В самом деле, какая разница пользователю, отчего
                      именно погибла ваша программа? Пользователь лишь должен сообщить, что ваша
                      программа работает с ошибкой и передать информацию, которая поможет эту
                      ошибку найти. Конечно, подобный уникальный номер занимает некоторое место
                      в сегменте констант, но, как показывает практика, не больше 5% от всего
                      объема программы, и в добавление, такой номер очень удобно записывать с
                      экрана. Кроме того, он действительно никогда не повторяется! Если же вы —
                      эконом, и вам жалко тратить 38 байт на каждый идентификатор, можете
                      передавать в процедуру AssertMsg четырехбайтное число, которое можно
                      получить, взяв первые восемь шестнадцатеричных цифр из строкового
                      идентификатора — именно они изменяются чаще всего.

                      AssertMsg(FCount > 0, $19619100);

                      4. Категории ошибок

                      В последнее время,
                      практически во всех развитых объектно-ориентированных языках, появилась
                      структурная обработка исключений. Эта возможность поддерживается
                      операционной системой и позволяет перехватывать нештатные ситуации
                      возникающие, как в обычном, так и в защищенном режиме системы. Язык Object
                      Pascal полностью поддерживает все возможности по обработке исключений.
                      Использование исключений при разработке программ рекомендуется
                      документацией и поддерживается широким использованием исключений в VCL.
                      Типичный пример обработки исключений приведен ниже.

                      try
                        FS := TMemoryStream.Create;
                        try
                          FS.LoadFromFile('sound.dat');
                          ...
                        finally
                          FS.Free;
                        end;
                      except
                        FSoundData := nil;
                        raise;
                      end;

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

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

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

                      Прежде всего, ошибки нужно разделить на две группы — фатальные ошибки и
                      нефатальные или восстановимые ошибки. Фатальные ошибки — это ошибки
                      «несовместимые с жизнью», после которых выполнение программы невозможно и
                      бессмысленно. Фатальная ошибка всегда является неожиданной и
                      подразумевается маловероятной. Например, любое срабатывание проверки
                      Assert означает серьезное нарушение логики работы программы. Сбой при
                      выделении памяти или ресурсов в большинстве случаев также является
                      невосстановимым. Подавляющее большинство API функций при передаче в них
                      правильных параметров можно отнести к «надежным» функциям, то есть таким
                      функциям, в выполнении которых мы уверены, и невыполнение которых
                      маловероятно, но приводит к фатальным ошибкам. Фатальные ошибки можно
                      отнести к серьезным недостаткам либо в программе, либо в операционной
                      системе.

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

                      Грань между фатальными и восстановимыми ошибками очень тонка и
                      неопределенна. В одном случае ошибку можно отнести к фатальным ошибкам, а
                      в другом — к восстановимым. Но, тем не менее, каждая нештатная ситуация
                      должна быть четко отнесена к одному из этих двух классов.

                      В пределе такого подхода, все функции должны быть объявлены
                      ненадежными, и должно быть предсмотрено восстановление нормальной работы
                      программы после любой нештатной ситуации. Однако, такое вряд ли возможно,
                      так как в таком случае 99% от объема программы будут занимать проверки и
                      восстановления после сбоя при выполнения любой из функции. Таким образом,
                      для успешной и эффективной работы, необходимо как можно больше функций
                      отнести к разряду надежных, и как можно меньше — к разряду ненадежных,
                      требующих специальной обработки.

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

                      Таким образом, мы пришли к пониманию того, что существуют разные группы
                      ошибок, с разным алгоритмом их обработки. Как уже упоминалось, все ошибки
                      в библиотеке VCL обрабатываются одинаковым образом. Изменить существующее
                      положение вещей можно разными способами. Например, в можно анализировать
                      все типы исключений в обработчике TApplication.OnException. Исключения
                      таких типов как, например, EAccessViolation, EListError, EAbstractError,
                      EArrayError, EAssertionFailed и многих других можно рассматривать как
                      фатальные ошибки, а исключения остальных типов рассматривать как
                      восстановимые ошибки. При этом откат при восстановимых ошибках выполнять
                      путем локального перехвата исключения конструкцией try-except-end,
                      обработки и дальнейшей генерации исключения инструкцией raise. Однако
                      такой способ не является гибким, так как один и тот же тип исключения в
                      одной операции может рассматриваться как восстановимый, а в другой — как
                      фатальный.

                      Улучшенной разновидностью такого способа является способ, когда все
                      восстановимые исключения от VCL перехватываются и обрабатываются на
                      местах, а фатальные исключения транспортируются в глобальный обработчик
                      TApplication.OnException. Таким образом, глобальный обработчик
                      рассматривает любое исключение как фатальное. Перехват восстановимых
                      исключений не занимает много места, так как и было указано раньше,
                      подавляющее большинство операций можно и нужно рассматривать как
                      «надежные» операции, то есть приводящие к фатальным ошибкам.

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

                      try
                        // Потенциально опасная операция
                        FS := TFileStream('sound.dat', fmOpenRead);
                      except
                        ShowMessage('Невозможно открыть файл данных');
                        Exit;
                      end;
                      // Проверка размера
                      if FS.Size <> 1000 then
                        Exit;
                      // "Надежная" операция - все должно выполняться нормально
                      FS.Position := 0;
                      FS.ReadBuffer(Fbuffer, 1000);

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

                      procedure AssertWin32(Condition: Boolean; Mark: string);
                      var
                        ExceptAddr: Pointer;
                        ExceptMsg: string;
                      begin
                        // Если условие выполняется, то выходим
                        if Condition then
                          Exit;
                        // Получаем адрес вызвавшей нас инструкции Call
                        asm
                          mov EAX, [EBP+$04]
                          mov ExceptAddr, EAX
                        end;
                        // Получаем сообщение об ошибке
                        ExceptMsg := SysErrorMessage(GetLastError);
                        // Возбуждаем исключение в месте вызова этой функции
                        raise EFatalExcept.Create('Ошибка Win32: ' + ExceptMsg + #13#13+Mark)at
                          ExceptAddr;
                      end;

                      Таким образом, ключом к успеху является правильное разделение всех
                      вызовов функций в программе на две группы. Первая группа — потенциально
                      опасные, «ненадежные» функции должны быть обработаны с особой
                      тщательностью и возможностью восстановления программы после сбоя этих
                      функций. Ошибки при их выполнении вполне возможны и должны рассматриваться
                      как естественные. Вторая группа — «надежные» функции, невыполнение которых
                      должно приводить к выводу диагностического сообщения и закрытию программы.
                      Ошибки при выполнении надежных функций должны рассматриваться как
                      фатальные.

                      Вы либо полностью контролируете ситуацию, с возможностью отката к
                      нормальному состоянию программы — «нефатальная ошибка»; либо извиняетесь и
                      закрываете программу, так как вы не можете корректно обработать эту ошибку
                      — «фатальная» ошибка. Во втором случае вы должны сделать все возможное,
                      чтобы информация об ошибке была как можно содержательнее, для того, чтобы
                      вы могли ее исправить.

                      5. Тотальный контроль

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

                      Однако существует еще одна проблема, связанная с тем, что широко
                      используемая библиотека VCL, а также множество других естественно не
                      используют выше указанные принципы. Например, при выполнении следующего
                      кода возникнет исключение от VCL с сообщением «List index out of bounds»,
                      так как мы запрашиваем на один элемент больше, чем есть в списке. Получив
                      сообщение о такой ошибке от пользователя, вы вряд ли сможете определить
                      место программы, где эта ошибка произошла. Поиск такой ошибки затруднен,
                      даже если она возникла на вашем компьютере, на том, на котором вы
                      разрабатываете программу. А если такая ошибка возникла у далекого
                      пользователя, сложность возрастает во много раз, так как вам предстоит еще
                      и «выбить» всю информацию об ошибке у человека далекого от Delphi в
                      частности, и от программирования вообще.

                      for i := 0 to FList.Count do
                        FObj := TObject(FList[i]);

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

                      Одним из успешных вариантов решения такой проблемы является помещение
                      каждой (каждой !) процедуры в конструкцию try-except-end. При этом
                      локальный обработчик исключения ловит возникшую ошибку и заменяет ее своей
                      с указанием уникального идентификатора. При этом предыдущий пример может
                      выглядеть так.

                      try
                        for i := 0 to FList.Count do
                          FObj := TObject(FList[i]);
                      except
                        raise AssertInternal('{88A4613E-0ACB-11D4-ACCF-009027350D25}');
                      end;

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

                      Получив информацию о возникшей ошибке вы можете:

                      • с точностью до процедуры узнать место возникновения ошибки;
                      • узнать тип исключения и параметры ошибки (например ошибочный индекс
                        или адрес по которому произошел ошибочный доступ к памяти).

                      Такая информация значительно облегчает поиск и исправление
                      ошибок в очень большом проекте.

                      6. Заключение

                      Все вышеперечисленное являлось
                      лишь приблизительным рецептом и пищей для размышления. Точный вариант
                      комплексной системы обработки ошибок приведен в прилагаемом архиве. Модуль
                      ATSAssert.pas содержит примеры процедур и функций для обработки фатальных
                      ошибок. Модуль ATSDialogMain.pas содержит примеры их использования.

                      7. Пример модуля с функциями комплексной обработки
                      ошибок

                      ////////////////////////////////////////////////////////////////////////////////
                      //             ATSTest - иллюстрация комплексной обработки исключений
                      //                             (c) Николай Мазуркин
                      // _____________________________________________________________________________
                      //                      Процедуры обработки фатальных ошибок
                      ////////////////////////////////////////////////////////////////////////////////
                      
                      unit ATSAssert;
                      
                      interface
                      
                      uses Windows, Messages, SysUtils, ActiveX, Forms;

                      Объявления деклараций используемых функций

                      Объявляется перечень и
                      формат функций для комлексного контроля за качеством кода.

                      ////////////////////////////////////////////////////////////////////////////////
                      // Процедуры обработки фатальных ситуаций
                      ////////////////////////////////////////////////////////////////////////////////
                      procedure AssertMsg(Condition: Boolean; const Mark: string);
                      procedure AssertWin32(Condition: Boolean; const Mark: string);
                      function  AssertInternal(const Mark: string): TObject;
                      procedure HandleError(const Msg: string);
                      
                      implementation
                      
                      ////////////////////////////////////////////////////////////////////////////////
                      // Utils
                      // _____________________________________________________________________________
                      // Процедуры обработки фатальных ситуаций
                      ////////////////////////////////////////////////////////////////////////////////

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

                      Глобальный обработчик исключений
                      устанавливает процедуру обработки на событие Application.OnException. Это
                      позволяет перехватывать все исключения не пойманные конструкцией
                      try-except-AssertInternal-end. Хотя таких непойманных исключений быть не
                      должно, лучше все-таки поставить на них обработчик.

                      Если вы пишите библиотеку DLL в таком обработчике нет необходимости,
                      так как все непойманные исключения будут обрабатывать в EXE-модуле.

                      ////////////////////////////////////////////////////////////////////////////////
                      // TExceptHandler
                      // _____________________________________________________________________________
                      // Глобальный обработчик исключений
                      ////////////////////////////////////////////////////////////////////////////////
                      type
                        TExceptHandler = class(TObject)
                          procedure OnException(Sender: TObject; E: Exception);
                        end;
                      
                      var
                        ExceptHandler: TExceptHandler;
                      
                        ////////////////////////////////////////////////////////////////////////////////
                        // Обработка исключений
                        ////////////////////////////////////////////////////////////////////////////////
                      
                      procedure TExceptHandler.OnException(Sender: TObject; E: Exception);
                      begin
                        HandleError(PChar(E.Message));
                      end;

                      Основная процедура обработки ошибок

                      Основная процедура обработки
                      ошибок. Формирует и выводит сообщение, записывает его в посмертный лог и
                      закрывает программу. Если вы пишете библиотеку DLL, то для каждой
                      библиотеки можете добавить к сообщению свой дополнительный префикс,
                      например ‘A’, ‘B’, ‘C’ и т.д. Это поможет быстро определить в каком модуле
                      произошла ошибка.

                      ////////////////////////////////////////////////////////////////////////////////
                      // Обработка ошибочной ситуации
                      ////////////////////////////////////////////////////////////////////////////////
                      
                      procedure HandleError(const Msg: string);
                      var
                        DeadFile: TextFile;
                        DeadName: string;
                        ErrorMsg: string;
                      begin
                        // Формируем сообщение
                        ErrorMsg := Msg + #13#13;
                      // Если это исключение, то добавим его тип и адрес
                        if ExceptObject <> nil then
                          ErrorMsg := ErrorMsg + Format('Исключение типа %s, адрес 0x%8.8x'#13#13,
                            [ExceptObject.ClassName, DWord(ExceptAddr)]
                            );
                        ErrorMsg := ErrorMsg + 'Выполнение программы будет прекращено.';
                        // Показываем диалог
                        MessageBox(0, PChar(ErrorMsg),
                          'Фатальная ошибка', MB_OK + MB_ICONWARNING + MB_APPLMODAL + MB_SETFOREGROUND
                            + MB_TOPMOST);
                        // Генерируем уникальное имя файла
                        SetLength(DeadName, MAX_PATH + 10);
                        GetTempFileName(PChar(ExtractFilePath(ParamStr(0))), 'err', 0, @DeadName[1]);
                        // Записываем ошибку в посмертный лог - на случай если пользователь не догадается
                        // записать код ошибки и сообщение.
                        AssignFile(DeadFile, DeadName);
                        ReWrite(DeadFile);
                        WriteLn(DeadFile, ErrorMsg);
                        CloseFile(DeadFile);
                        // Немедленно закрываем процесс
                        ExitProcess(1);
                      end;

                      Альтернативная процедура Assert

                      ////////////////////////////////////////////////////////////////////////////////
                      // Проверка выполнимости условия
                      ////////////////////////////////////////////////////////////////////////////////
                      
                      procedure AssertMsg(Condition: Boolean; const Mark: string);
                      begin
                        // Если условие выполняется, то выходим сразу
                        if Condition then
                          Exit;
                        // Обрабатываем возникшую ошибку
                        HandleError('Фатальная ошибка ' + Mark);
                      end;

                      Альтернативная процедура Assert для API-функций

                      ////////////////////////////////////////////////////////////////////////////////
                      // Проверка выполнимости API-функции
                      ////////////////////////////////////////////////////////////////////////////////
                      
                      procedure AssertWin32(Condition: Boolean; const Mark: string);
                      var
                        ErrorMsg: string;
                      begin
                        // Если условие выполняется, то выходим
                        if Condition then
                          Exit;
                        // Получаем сообщение об ошибке
                        ErrorMsg := SysErrorMessage(GetLastError);
                        // Обрабатываем возникшую ошибку
                        HandleError('Фатальная ошибка ' + Mark + #13#13+ErrorMsg);
                      end;

                      Функция для тотального контроля
                      исключений

                      Функция для перехвата исключений от VCL и неудачных
                      обращений к памяти. Добаляет к тексту исключения уникальную метку и
                      генерирует ошибку.

                      ////////////////////////////////////////////////////////////////////////////////
                      // Генерация фатального исключения
                      ////////////////////////////////////////////////////////////////////////////////
                      
                      function AssertInternal(const Mark: string): TObject;
                      begin
                        // Проверяем тип сгенерированного исключения и обрабатываем возникшую ошибку
                        if ExceptObject is Exception then
                          HandleError('Фатальная ошибка ' + Mark + #13#13+
                           PChar(Exception(ExceptObject).Message))
                        else
                          HandleError('Фатальная ошибка ' + Mark);
                        // Следующая строчка никогда не выполнится, она нужна для успокоения компилятора
                        Result := nil;
                      end;

                      Инициализация модуля

                      Создание и настройка глобального обработчика
                      исключения на событии TApplication.OnException.

                      ////////////////////////////////////////////////////////////////////////////////
                      // Автоматическое создание глобального обработчика исключения
                      ////////////////////////////////////////////////////////////////////////////////
                      initialization
                        ExceptHandler := TExceptHandler.Create;
                        Application.OnException := ExceptHandler.OnException;
                      
                      end.

                      8. Пример использования системы комплексного
                      контроля

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

                      Исходные
                      тексты функций комплексного контроля и пример поясняющий их работу (8
                      кБ)

                      Обработка исключительных ситуаций в Delphi

                      Содержание

                        Обзор
                        Структурная обработка исключительных ситуаций
                        Модель исключительных ситуаций в Delphi
                        Синтаксис обработки исключительных ситуаций
                        Примеры обработки исключительных ситуаций
                        Вызов исключительной ситуации
                        Доступ к экземпляру объекта exception
                        Предопределенные обработчики исключительных ситуаций
                        Исключения, возникающие при работе с базами данных
                        Заключение

                      Обзор

                      С целью поддержки структурной обработки исключительных ситуаций (exception) в Delphi введены новые расширения языка Pascal. В данной статье будет дано описание того, что из себя представляет такая обработка, почему она полезна, будут приведены соответствующий синтаксис Object Pascal и примеры использования исключительных ситуаций в Delphi.

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

                      Структурная обработка исключительных ситуаций — это система, позволяющая программисту при возникновении ошибки (исключительной ситуации) связаться с кодом программы, подготовленным для обработки такой ошибки. Это выполняется с помощью языковых конструкций, которые как бы «охраняют» фрагмент кода программы и определяют обработчики ошибок, которые будут вызываться, если что-то пойдет не так в «охраняемом» участке кода. В данном случае понятие исключительной ситуации относится к языку и не нужно его путать с системными исключительными ситуациями (hardware exceptions), такими как General Protection Fault. Эти исключительные ситуации обычно используют прерывания и особые состояния «железа» для обработки критичной системной ошибки; исключительные ситуации в Delphi же независимы от «железа», не используют прерываний и используются для обработки ошибочных состояний, с которыми подпрограмма не готова иметь дело. Системные исключительные ситуации, конечно, могут быть перехвачены и преобразованы в языковые исключительные ситуации, но это только одно из применений языковых исключительных ситуаций.

                      При традиционной обработке ошибок, ошибки, обнаруженные в процедуре обычно передаются наружу (в вызывавшую процедуру) в виде возвращаемого значения функции, параметров или глобальных переменных (флажков). Каждая вызывающая процедура должна проверять результат вызова на наличие ошибки и выполнять соответствующие действия. Часто, это просто выход еще выше, в более верхнюю вызывающую процедуру и т.д. : функция A вызывает B, B вызывает C, C обнаруживает ошибку и возвращает код ошибки в B, B проверяет возвращаемый код, видит, что возникла ошибка и возвращает код ошибки в A, A проверяет возвращаемый код и выдает сообщение об ошибке либо решает сделать что-нибудь еще, раз первая попытка не удалась.

                      Такая «пожарная бригада» для обработки ошибок трудоемка, требует написания большого количества кода в котором можно легко ошибиться и который трудно отлаживать.

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

                      Структурная обработка исключительной ситуации замещает ручную обработку ошибок автоматической, сгенерированной компилятором системой уведомления. В приведенном выше примере, процедура A установила бы «охрану» со связанным обработчиком ошибки на фрагмент кода, в котором вызывается B. B просто вызывает C. Когда C обнаруживает ошибку, то создает (raise) исключительную ситуацию. Специальный код, сгенерированный компилятором и встроенный в Run-Time Library (RTL) начинает поиск обработчика данной исключительной ситуации. При поиске «защищенного» участка кода используется информация, сохраненная в стеке. В процедурах C и B нет такого участка, а в A — есть. Если один из обработчиков ошибок, которые используются в A, подходит по типу для возникшей в C исключительной ситуации, то программа переходит на его выполнение. При этом, область стека, используемая в B и C, очищается; выполнение этих процедур прекращается.

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

                      Без проверки возвращаемого кода после каждого вызова подпрограммы, код программы должен быть более простым, а скомпилированный код — более быстрым. При наличии исключительных ситуаций подпрограмма B не должна содержать дополнительный код для проверки возвращаемого результата и передачи его в A. B ничего не должна делать для передачи исключительной ситуации, возникшей в C, в процедуру A — встроенная система обработки исключительных ситуаций делает всю работу.

                      Данная система называется структурной, поскольку обработка ошибок определяется областью «защищенного» кода; такие области могут быть вложенными. Выполнение программы не может перейти на произвольный участок кода; выполнение программы может перейти только на обработчик исключительной ситуации активной программы.

                      Модель исключительных ситуаций в Delphi

                      Модель исключительных ситуаций в Object Pascal является невозобновляемой(non-resumable). При возникновении исключительной ситуации Вы уже не сможете вернуться в точку, где она возникла, для продолжения выполнения программы (это позволяет сделать возобновляемая(resumable) модель). Невозобновляемые исключительные ситуации разрушают стек, поскольку они сканируют его в поисках обработчика; в возобновляемой модели необходимо сохранять стек, состояние регистров процессора в точке возникновения ошибки и выполнять поиск обработчика и его выполнение в отдельном стеке. Возобновляемую систему обработки исключительных ситуаций гораздо труднее создать и применять, нежели невозобновляемую.

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

                      Теперь, когда мы рассмотрели, что такое исключительные ситуации, давайте дадим ясную картину, как они применяются. Новое ключевое слово, добавленное в язык Object Pascal — try. Оно используется для обозначения первой части защищенного участка кода. Существует два типа защищенных участков:

                      • try..except
                      • try..finally

                      Первый тип используется для обработки исключительных ситуаций. Его синтаксис:

                      try
                        Statement 1;
                        Statement 2;
                        ...
                      except
                        on Exception1 do Statement;
                        on Exception2 do Statement;
                        ...
                      else
                        Statements; {default exception-handler}
                      end;

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

                      try
                        Statement1;
                        Statement2;
                        ...
                      finally
                        Statements;  { These statements always execute }
                      end;

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

                      Ниже приведены процедуры A,B и C, обсуждавшиеся ранее, воплощенные в новом синтаксисе Object Pascal:

                      type
                        ESampleError = class(Exception);
                      var
                        ErrorCondition: Boolean;
                      procedure C;
                      begin
                        writeln('Enter C');
                        if (ErrorCondition) then
                        begin
                          writeln('Raising exception in C');
                          raise ESampleError.Create('Error!');
                        end;
                        writeln('Exit C');
                      end;
                      procedure B;
                      begin
                        writeln('enter B');
                        C;
                        writeln('exit B');
                      end;
                      procedure A;
                      begin
                        writeln('Enter A');
                        try
                          writeln('Enter A''s try block');
                          B;
                          writeln('After B call');
                        except
                          on ESampleError do
                            writeln('Inside A''s ESampleError handler');
                          on ESomethingElse do
                            writeln('Inside A''s ESomethingElse handler');
                        end;
                        writeln('Exit A');
                      end;
                      begin
                        writeln('begin main');
                        ErrorCondition := True;
                        A;
                        writeln('end main');
                      end. 

                      При ErrorCondition = True программа выдаст:

                      begin main
                      Enter A
                      Enter A's try block
                      enter B
                      Enter C
                      Raising exception in C
                      Inside A's ESampleError handler
                      Exit A
                      end main

                      Возможно вас удивила декларация типа ‘ESampleError =class’ вместо ‘=object’; это еще одно новое расширение языка. Delphi вводит новую модель объектов, доступную через декларацию типа ‘=class’. Описание новой объектной модели дается в других уроках. Здесь же достаточно сказать, что исключительные ситуации (exceptions) являются классами, частью новой объектной модели.

                      Процедура C проверяет наличие ошибки (в нашем случае это значение глобальной переменной) и, если она есть (а это так), C вызывает(raise) исключительную ситуацию класса ESampleError.

                      Процедура A помещает часть кода в блок try..except. Первая часть этого блока содержит часть кода, аналогично конструкции begin..end. Эта часть кода завершается ключевым словом except, далее следует один или более обработчиков исключительных ситуаций on xxxx do yyyy, далее может быть включен необязательный блок else, вся конструкция заканчивается end;. В конструкции, назначающей определенную обработку для конкретной исключительной ситуации (on xxxx do yyyy), после резервного слова on указывается класс исключительной ситуации, а после do следует собственно код обработки данной ошибки. Если возникшая исключительная ситуация подходит по типу к указанному после on, то выполнение программы переходит сюда (на код после do). Исключительная ситуация подходит в том случае, если она того же класса, что указан в on, либо является его потомком. Например, в случае on EFileNotFound обрабатываться будет ситуация, когда файл не найден. А в случае on EFileIO — все ошибки при работе с файлами, в том числе и предыдущая ситуация. В блоке else обрабатываются все ошибки, не обработанные до этого.

                      Приведенные в примере процедуры содержат код (строка с writeln), который отображает путь выполнения программы. Когда C вызывает exception, программа сразу переходит на обработчик ошибок в процедуре A, игнорируя оставшуюся часть кода в процедурах B и C.

                      После того, как найден подходящий обработчик ошибки, поиск оканчивается. После выполнения кода обработчика, программа продолжает выполняться с оператора, стоящего после слова end блока try..except (в примере — writeln(‘Exit A’)).

                      Конструкция try..except подходит, если известно, какой тип ошибок нужно обрабатывать в конкретной ситуации. Но что делать, если требуется выполнить некоторые действия в любом случае, произошла ошибка или нет? Это тот случай, когда понадобится конструкция try..finally.

                      Рассмотрим модифицированную процедуру B:

                      procedure NewB;
                      var
                        P: Pointer;
                      begin
                        writeln('enter B');
                        GetMem(P, 1000);
                        C;
                        FreeMem(P, 1000);
                        writeln('exit B');
                      end;

                      Если C вызывает исключительную ситуацию, то программа уже не возвращается в процедуру B. А что же с теми 1000 байтами памяти, захваченными в B? Строка FreeMem(P,1000) не выполнится и Вы потеряете кусок памяти. Как это исправить? Нужно ненавязчиво включить процедуру B в процесс, например:

                      procedure NewB;
                      var
                        P: Pointer;
                      begin
                        writeln('enter NewB');
                        GetMem(P, 1000);
                        try
                          writeln('enter NewB''s try block');
                          C;
                          writeln('end of NewB''s try block');
                        finally
                          writeln('inside NewB''s finally block');
                          FreeMem(P, 1000);
                        end;
                        writeln('exit NewB');
                      end;

                      Если в A поместить вызов NewB вместо B, то программа выведет сообщения следующим образом:

                      begin main
                      Enter A
                      Enter A's try block
                      enter NewB
                      enter NewB's try block
                      Enter C
                      Raising exception in C
                      inside NewB's finally block
                      Inside A's ESampleError handler
                      Exit A
                      end main

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

                      Почему вызов GetMem не помещен внутрь блока try? Этот вызов может окончиться неудачно и вызвать exception EOutOfMemory. Если это произошло, то FreeMem попытается освободить память, которая не была распределена. Когда мы размещаем GetMem вне защищаемого участка, то предполагаем, что B сможет получить нужное количество памяти, а если нет, то более верхняя процедура получит уведомление EOutOfMemory.

                      А что, если требуется в B распределить 4 области памяти по схеме все-или-ничего? Если первые две попытки удались, а третья провалилась, то как освободить захваченную область память? Можно так:

                      procedure NewB;
                      var
                        p,q,r,s: Pointer;
                      begin
                        writeln('enter B');
                        P := nil;
                        Q := nil;
                        R := nil;
                        S := nil;
                        try
                          writeln('enter B''s try block');
                          GetMem(P, 1000);
                          GetMem(Q, 1000);
                          GetMem(R, 1000);
                          GetMem(S, 1000);
                          C;
                          writeln('end of B''s try block');
                        finally
                          writeln('inside B''s finally block');
                          if P <> nil then FreeMem(P, 1000);
                          if Q <> nil then FreeMem(Q, 1000);
                          if R <> nil then FreeMem(R, 1000);
                          if S <> nil then FreeMem(S, 1000);
                        end;
                        writeln('exit B');
                      end;

                      Установив сперва указатели в NIL, далее можно определить, успешно ли прошел вызов GetMem.

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

                      Вызов исключительной ситуации

                      В процедуре C из примера мы уже могли видеть, как должна поступать программа при обнаружении состояния ошибки — она вызывает исключительную ситуацию:

                      raise ESampleError.Create('Error!');

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

                      Почти все существующие классы исключительных ситуаций являются наследниками базового класса Exception и не содержат новых свойств или методов. Класс Exception имеет несколько конструкторов, какой из них конкретно использовать — зависит от задачи. Описание класса Exception можно найти в on-line Help.

                      Доступ к экземпляру объекта exception

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

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

                      Рассмотрим модифицированную процедуру A в нашем примере:

                      procedure NewA;
                      begin
                        writeln('Enter A');
                        try
                          writeln('Enter A''s try block');
                          B;
                          writeln('After B call');
                        except
                          on E: ESampleError do writeln(E.Message);
                          on ESomethingElse do
                            writeln('Inside A''s ESomethingElse handler');
                        end;
                        writeln('Exit A');
                      end;

                      Здесь все изменения внесены в строку

                          on ESE: ESampleError do writeln(ESE.Message);

                      Пример демонстрирует еще одно новшество в языке Object Pascal — создание локальной переменной. В нашем примере локальной переменной является ESE — это тот самый экземпляр класса ESampleError, который был создан в процедуре C в момент вызова исключительного состояния. Переменная ESE доступна только внутри блока do. Свойство Message объекта ESE содержит сообщение, которое было передано в конструктор Create в процедуре C.

                      Есть еще один способ доступа к экземпляру exception — использовать функцию ExceptionObject:

                      on ESampleError do
                        writeln(ESampleError(ExceptionObject).Message);

                      Предопределенные обработчики исключительных ситуаций

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

                      • Exception — базовый класс-предок всех обработчиков исключительных ситуаций.
                      • EAbort — «скрытое» исключение. Используйте его тогда, когда хотите прервать тот или иной процесс с условием, что пользователь программы не должен видеть сообщения об ошибке. Для повышения удобства использования в модуле SysUtils предусмотрена процедура Abort, определенная, как:
                      • procedure Abort;
                        begin
                          raise EAbort.CreateRes(SOperationAborted) at ReturnAddr;
                        end; 
                      • EComponentError — вызывается в двух ситуациях:
                        1. при попытке регистрации компоненты за пределами процедуры Register;
                        2. когда имя компоненты не уникально или не допустимо.
                      • EConvertError — происходит в случае возникновения ошибки при выполнении функций StrToInt и StrToFloat, когда конвертация строки в соответствующий числовой тип невозможна.
                      • EInOutError — происходит при ошибках ввода/вывода при включенной директиве {$I+}.
                      • EIntError — предок исключений, случающихся при выполнении целочисленных операций.
                        • EDivByZero — вызывается в случае деления на ноль, как результат RunTime Error 200.
                        • EIntOverflow — вызывается при попытке выполнения операций, приводящих к переполнению целых переменных, как результат RunTime Error 215 при включенной директиве {$Q+}.
                        • ERangeError — вызывается при попытке обращения к элементам массива по индексу, выходящему за пределы массива, как результат RunTime Error 201 при включенной директиве {$R+}.
                      • EInvalidCast — происходит при попытке приведения переменных одного класса к другому классу, несовместимому с первым (например, приведение переменной типа TListBox к TMemo).
                      • EInvalidGraphic — вызывается при попытке передачи в LoadFromFile файла, несовместимого графического формата.
                      • EInvalidGraphicOperation — вызывается при попытке выполнения операций, неприменимых для данного графического формата (например, Resize для TIcon).
                      • EInvalidObject — реально нигде не используется, объявлен в Controls.pas.
                      • EInvalidOperation — вызывается при попытке отображения или обращения по Windows-обработчику (handle) контрольного элемента, не имеющего владельца (например, сразу после вызова MyControl:=TListBox.Create(…) происходит обращение к методу Refresh).
                      • EInvalidPointer — происходит при попытке освобождения уже освобожденного или еще неинициализированного указателя, при вызове Dispose(), FreeMem() или деструктора класса.
                      • EListError — вызывается при обращении к элементу наследника TList по индексу, выходящему за пределы допустимых значений (например, объект TStringList содержит только 10 строк, а происходит обращение к одиннадцатому).
                      • EMathError — предок исключений, случающихся при выполнении операций с плавающей точкой.
                        • EInvalidOp — происходит, когда математическому сопроцессору передается ошибочная инструкция. Такое исключение не будет до конца обработано, пока Вы контролируете сопроцессор напрямую из ассемблерного кода.
                        • EOverflow — происходит как результат переполнения операций с плавающей точкой при слишком больших величинах. Соответствует RunTime Error 205.
                        • Underflow — происходит как результат переполнения операций с плавающей точкой при слишком малых величинах. Соответствует RunTime Error 206.
                        • EZeroDivide — вызывается в результате деления на ноль.
                      • EMenuError — вызывается в случае любых ошибок при работе с пунктами меню для компонент TMenu, TMenuItem, TPopupMenu и их наследников.
                      • EOutlineError — вызывается в случае любых ошибок при работе с TOutLine и любыми его наследниками.
                      • EOutOfMemory — происходит в случае вызовов New(), GetMem() или конструкторов классов при невозможности распределения памяти. Соответствует RunTime Error 203.
                      • EOutOfResources — происходит в том случае, когда невозможно выполнение запроса на выделение или заполнение тех или иных Windows ресурсов (например таких, как обработчики — handles).
                      • EParserError — вызывается когда Delphi не может произвести разбор и перевод текста описания формы в двоичный вид (часто происходит в случае исправления текста описания формы вручную в IDE Delphi).
                      • EPrinter — вызывается в случае любых ошибок при работе с принтером.
                      • EProcessorException — предок исключений, вызываемых в случае прерывания процессора- hardware breakpoint. Никогда не включается в DLL, может обрабатываться только в «цельном» приложении.
                        • EBreakpoint — вызывается в случае останова на точке прерывания при отладке в IDE Delphi. Среда Delphi обрабатывает это исключение самостоятельно.
                        • EFault — предок исключений, вызываемых в случае невозможности обработки процессором тех или иных операций.
                          • EGPFault — вызывается, когда происходит «общее нарушение защиты» — General Protection Fault. Соответствует RunTime Error 216.
                          • EInvalidOpCode — вызывается, когда процессор пытается выполнить недопустимые инструкции.
                          • EPageFault — обычно происходит как результат ошибки менеджера памяти Windows, вследствие некоторых ошибок в коде Вашего приложения. После такого исключения рекомендуется перезапустить Windows.
                          • EStackFault — происходит при ошибках работы со стеком, часто вследствие некорректных попыток доступа к стеку из фрагментов кода на ассемблере. Компиляция Ваших программ со включенной проверкой работы со стеком {$S+} помогает отследить такого рода ошибки.
                        • ESingleStep — аналогично EBreakpoint, это исключение происходит при пошаговом выполнении приложения в IDE Delphi, которая сама его и обрабатывает.
                      • EPropertyError — вызывается в случае ошибок в редакторах свойств, встраиваемых в IDE Delphi. Имеет большое значение для написания надежных property editors. Определен в модуле DsgnIntf.pas.
                      • EResNotFound — происходит в случае тех или иных проблем, имеющих место при попытке загрузки ресурсов форм — файлов .DFM в режиме дизайнера. Часто причиной таких исключений бывает нарушение соответствия между определением класса формы и ее описанием на уровне ресурса (например,вследствие изменения порядка следования полей-ссылок на компоненты, вставленные в форму в режиме дизайнера).
                      • EStreamError — предок исключений, вызываемых при работе с потоками.
                        • EFCreateError — происходит в случае ошибок создания потока (например, при некорректном задании файла потока).
                        • EFilerError — вызывается при попытке вторичной регистрации уже зарегистрированного класса (компоненты). Является, также, предком специализированных обработчиков исключений, возникающих при работе с классами компонент.
                          • EClassNotFound — обычно происходит, когда в описании класса формы удалено поле-ссылка на компоненту, вставленную в форму в режиме дизайнера. Вызывается, в отличие от EResNotFound, в RunTime.
                          • EInvalidImage — вызывается при попытке чтения файла, не являющегося ресурсом, или разрушенного файла ресурса, специализированными функциями чтения ресурсов (например, функцией ReadComponent).
                          • EMethodNotFound — аналогично EClassNotFound, только при несоответствии методов, связанных с теми или иными обработчиками событий.
                          • EReadError — происходит в том случае, когда невозможно прочитать значение свойства или другого набора байт из потока (в том числе ресурса).
                        • EFOpenError — вызывается когда тот или иной специфированный поток не может быть открыт (например, когда поток не существует).
                      • EStringListError — происходит при ошибках работы с объектом TStringList (кроме ошибок, обрабатываемых TListError).

                      Исключения, возникающие при работе с базами данных

                      Delphi, обладая прекрасными средствами доступа к данным, основывающимися на интерфейсе IDAPI, реализованной в виде библиотеки Borland Database Engine (BDE), включает ряд обработчиков исключительных ситуаций для регистрации ошибок в компонентах VCL работающим с БД. Дадим краткую характеристику основным из них:

                      • EDatabaseError — наследник Exception ; происходит при ошибках доступа к данным в компонентах-наследниках TDataSet. Объявлено в модуле DB. Ниже приведен пример из Delphi On-line Help, посвященный этому исключению:
                      • repeat {пока не откроем таблицу или не нажмем кнопку  Cancel}
                          try
                            Table1.Active := True; {Пытаемся открыть таблицу}
                            Break; { Если нет ошибки - прерваем цикл}
                          except
                            on EDatabaseError do
                              {Если нажата OK - повторяем попытку открытия  Table1}
                              if MessageDlg('Не могу открыть  Table1', mtError,
                        			[mbOK, mbCancel], 0) <> mrOK
                              then
                                raise;
                          end;
                        until False;
                        • EDBEngineError — наследник EDatabaseError ; вызывается, когда происходят
                          ошибки BDE или на сервере БД. Объявлено в модуле DB:
                        • EDBEngineError = class(EDatabaseError)
                          private
                            FErrors: TList;
                            function GetError(Index: Integer): TDBError;
                            function GetErrorCount: Integer;
                          public
                            constructor Create(ErrorCode: DBIResult);
                            destructor Destroy;
                            property ErrorCount: Integer;
                            property Errors[Index: Integer]: TDBError;
                          end;

                          Особенно важны два свойства класса EDBEngineError : Errors
                          — список всех ошибок, находящихся в стеке ошибок BDE. Индекс первой ошибки
                          0;
                          ErrorCount — количество ошибок в стеке.
                          Объекты, содержащиеся в Errors, имеют тип TDBError. Доступные свойства класса
                          TDBError:
                          ErrorCode — код ошибки, возвращаемый Borland Database Engine;
                          Category — категория ошибки, описанной в ErrorCode;
                          SubCode — ‘субкод’ ошибки из ErrorCode;
                          NativeError — ошибка, возвращаемая сервером БД. Если NativeError 0, то ошибка
                          в ErrorCode не от сервера;
                          Message — сообщение, переданное сервером, если NativeError не равно 0; сообщение
                          BDE — в противном случае.

                      • EDBEditError — наследник Exception ; вызывается, когда данные не совместимы с маской ввода, наложенной на поле. Объявлено в модуле Mask.

                      Заключение

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

                      Назад | Содержание
                      | Вперед

                      Новости мира IT:

                      • 30.01 — Роскомнадзор получил более 44 тыс. жалоб о неправомерной обработке персональных данных в 2022 году
                      • 30.01 — На Apple подали в суд из-за сбора данных пользователей
                      • 30.01 — «Ростелеком», возможно, интересуется покупкой «Мегафона»
                      • 30.01 — В идеале Apple стремится создать очки дополненной реальности, которые можно носить весь день
                      • 30.01 — Российские мобильные операторы перешли на отечественную техподдержку
                      • 30.01 — Продажа «Билайна» российскому топ-менеджменту затягивается
                      • 27.01 — «Яндекс» попал в десятку самых посещаемых сайтов в мире
                      • 27.01 — В списке кредиторов криптобиржи FTX оказались Apple, Google, Amazon, Microsoft, а также авиакомпании, СМИ и университеты
                      • 27.01 — IBM и SAP показали хорошие квартальные результаты, но всё равно сокращают рабочие места
                      • 27.01 — От антимонопольного иска против Google выиграет Apple и другие компании
                      • 26.01 — Власти США подали иск, чтобы заблокировать сделку между Microsoft и Activision Blizzard
                      • 26.01 — Apple намерена вытеснить сервисы Google из iPhone — грядут «яблочные» заменители для рекламы и даже поиска
                      • 26.01 — Минюст США потребовал от Google раздробить рекламное подразделение и выплатить компенсации за завышение цен
                      • 26.01 — ЕС готовит антимонопольное расследование против Microsoft из-за сервиса Teams
                      • 26.01 — Российские компании активизировали наем IT-специалистов — число вакансий растёт
                      • 26.01 — Создан простой для вживления мозговой имплантат, который позволит набирать текст силой мысли
                      • 25.01 — Представлен стабильный релиз Wine 8.0
                      • 25.01 — «Яндекс» научил нейросеть расшифровывать архивные документы даже с дореволюционной орфографией
                      • 25.01 — Объём атак на российских операторов связи снизился в 2022 году
                      • 25.01 — В России возникли проблемы с доступом к Apple App Store и некоторым другим сервисам

                      Архив новостей

                      • Perfect Quality Hosting – компания со своим подходом к услугам аренды серверов
                      • Создали сайт – выберите для него достойный хостинг
                      • Гиперконвергентные решения: что нужно знать при переходе на них

                      Go Up to Classes and Objects Index

                      This topic covers the following material:

                      • A conceptual overview of exceptions and exception handling
                      • Declaring exception types
                      • Raising and handling exceptions

                      Contents

                      • 1 About Exceptions
                      • 2 When To Use Exceptions
                      • 3 Declaring Exception Types
                      • 4 Raising and Handling Exceptions
                        • 4.1 Try…except Statements
                        • 4.2 Re-raising Exceptions
                        • 4.3 Nested Exceptions
                        • 4.4 Try…finally Statements
                      • 5 Standard Exception Classes and Routines
                      • 6 See Also

                      About Exceptions

                      An exception is raised when an error or other event interrupts normal execution of a program. The exception transfers control to an exception handler, which allows you to separate normal program logic from error-handling. Because exceptions are objects, they can be grouped into hierarchies using inheritance, and new exceptions can be introduced without affecting existing code. An exception can carry information, such as an error message, from the point where it is raised to the point where it is handled.

                      When an application uses the SysUtils unit, most runtime errors are automatically converted into exceptions. Many errors that would otherwise terminate an application — such as insufficient memory, division by zero, and general protection faults — can be caught and handled.

                      When To Use Exceptions

                      Exceptions provide an elegant way to trap runtime errors without halting the program and without awkward conditional statements. The requirements imposed by exception handling semantics impose a code/data size and runtime performance penalty. While it is possible to raise exceptions for almost any reason, and to protect almost any block of code by wrapping it in a try…except or try…finally statement, in practice these tools are best reserved for special situations.

                      Exception handling is appropriate for errors whose chances of occurring are low or difficult to assess, but whose consequences are likely to be catastrophic (such as crashing the application); for error conditions that are complicated or difficult to test for in if…then statements; and when you need to respond to exceptions raised by the operating system or by routines whose source code you don’t control. Exceptions are commonly used for hardware, memory, I/O, and operating-system errors.

                      Conditional statements are often the best way to test for errors. For example, suppose you want to make sure that a file exists before trying to open it. You could do it this way:

                      try
                          AssignFile(F, FileName);
                          Reset(F);     // raises an EInOutError exception if file is not found
                      except
                          on Exception do ...
                      end;
                      

                      But you could also avoid the overhead of exception handling by using:

                      if FileExists(FileName) then    // returns False if file is not found; raises no exception
                      
                      begin
                          AssignFile(F, FileName);
                          Reset(F);
                      end;
                      

                      Assertions provide another way of testing a Boolean condition anywhere in your source code. When an Assert statement fails, the program either halts with a runtime error or (if it uses the SysUtils unit) raises an SysUtils.EAssertionFailed exception. Assertions should be used only to test for conditions that you do not expect to occur.

                      Declaring Exception Types

                      Exception types are declared just like other classes. In fact, it is possible to use an instance of any class as an exception, but it is recommended that exceptions be derived from the SysUtils.Exception class defined in SysUtils.

                      You can group exceptions into families using inheritance. For example, the following declarations in SysUtils define a family of exception types for math errors:

                      type
                         EMathError = class(Exception);
                         EInvalidOp = class(EMathError);
                         EZeroDivide = class(EMathError);
                         EOverflow = class(EMathError);
                         EUnderflow = class(EMathError);
                      

                      Given these declarations, you can define a single SysUtils.EMathError exception handler that also handles SysUtils.EInvalidOp, SysUtils.EZeroDivide, SysUtils.Overflow, and SysUtils.EUnderflow.

                      Exception classes sometimes define fields, methods, or properties that convey additional information about the error. For example:

                      type EInOutError = class(Exception)
                             ErrorCode: Integer;
                           end;
                      

                      Raising and Handling Exceptions

                      To raise an exception object, use an instance of the exception class with a raise statement. For example:

                      raise EMathError.Create;
                      

                      In general, the form of a raise statement is

                      raise object at address
                      

                      where object and at address are both optional. When an address is specified, it can be any expression that evaluates to a pointer type, but is usually a pointer to a procedure or function. For example:

                      raise Exception.Create('Missing parameter') at @MyFunction;
                      

                      Use this option to raise the exception from an earlier point in the stack than the one where the error actually occurred.

                      When an exception is raised — that is, referenced in a raise statement — it is governed by special exception-handling logic. A raise statement never returns control in the normal way. Instead, it transfers control to the innermost exception handler that can handle exceptions of the given class. (The innermost handler is the one whose try…except block was most recently entered but has not yet exited.)

                      For example, the function below converts a string to an integer, raising an SysUtils.ERangeError exception if the resulting value is outside a specified range.

                      function StrToIntRange(const S: string; Min, Max: Longint): Longint;
                      begin
                          Result := StrToInt(S);   // StrToInt is declared in SysUtils
                          if (Result < Min) or (Result > Max) then
                             raise ERangeError.CreateFmt('%d is not within the valid range of %d..%d', [Result, Min, Max]);
                      end;
                      

                      Notice the CreateFmt method called in the raise statement. SysUtils.Exception and its descendants have special constructors that provide alternative ways to create exception messages and context IDs.

                      A raised exception is destroyed automatically after it is handled. Never attempt to destroy a raised exception manually.

                      Note: Raising an exception in the initialization section of a unit may not produce the intended result. Normal exception support comes from the SysUtils unit, which must be initialized before such support is available. If an exception occurs during initialization, all initialized units — including SysUtils — are finalized and the exception is re-raised. Then the exception is caught and handled, usually by interrupting the program. Similarly, raising an exception in the finalization section of a unit may not lead to the intended result if SysUtils has already been finalized when the exception has been raised.

                      Try…except Statements

                      Exceptions are handled within try…except statements. For example:

                      try
                         X := Y/Z;
                         except
                           on EZeroDivide do HandleZeroDivide;
                      end;
                      

                      This statement attempts to divide Y by Z, but calls a routine named HandleZeroDivide if an SysUtils.EZeroDivide exception is raised.

                      The syntax of a try…except statement is:

                      try statements except exceptionBlock end
                      

                      where statements is a sequence of statements (delimited by semicolons) and exceptionBlock is either:

                      • another sequence of statements or
                      • a sequence of exception handlers, optionally followed by
                      else statements
                      

                      An exception handler has the form:

                      on identifier: type do statement

                      where identifier: is optional (if included, identifier can be any valid identifier), type is a type used to represent exceptions, and statement is any statement.

                      A try…except statement executes the statements in the initial statements list. If no exceptions are raised, the exception block (exceptionBlock) is ignored and control passes to the next part of the program.

                      If an exception is raised during execution of the initial statements list, either by a raise statement in the statements list or by a procedure or function called from the statements list, an attempt is made to ‘handle’ the exception:

                      • If any of the handlers in the exception block matches the exception, control passes to the first such handler. An exception handler ‘matches’ an exception just in case the type in the handler is the class of the exception or an ancestor of that class.
                      • If no such handler is found, control passes to the statement in the else clause, if there is one.
                      • If the exception block is just a sequence of statements without any exception handlers, control passes to the first statement in the list.

                      If none of the conditions above is satisfied, the search continues in the exception block of the next-most-recently entered try…except statement that has not yet exited. If no appropriate handler, else clause, or statement list is found there, the search propagates to the next-most-recently entered try…except statement, and so forth. If the outermost try…except statement is reached and the exception is still not handled, the program terminates.

                      When an exception is handled, the stack is traced back to the procedure or function containing the try…except statement where the handling occurs, and control is transferred to the executed exception handler, else clause, or statement list. This process discards all procedure and function calls that occurred after entering the try…except statement where the exception is handled. The exception object is then automatically destroyed through a call to its Destroy destructor and control is passed to the statement following the try…except statement. (If a call to the Exit, Break, or Continue standard procedure causes control to leave the exception handler, the exception object is still automatically destroyed.)

                      In the example below, the first exception handler handles division-by-zero exceptions, the second one handles overflow exceptions, and the final one handles all other math exceptions. SysUtils.EMathError appears last in the exception block because it is the ancestor of the other two exception classes; if it appeared first, the other two handlers would never be invoked:

                      try
                        ...
                      except
                        on EZeroDivide do HandleZeroDivide;
                        on EOverflow do HandleOverflow;
                        on EMathError do HandleMathError;
                      end;
                      

                      An exception handler can specify an identifier before the name of the exception class. This declares the identifier to represent the exception object during execution of the statement that follows on…do. The scope of the identifier is limited to that statement. For example:

                      try
                        ...
                      except
                        on E: Exception do ErrorDialog(E.Message, E.HelpContext);
                      end;
                      

                      If the exception block specifies an else clause, the else clause handles any exceptions that aren’t handled by the block’s exception handlers. For example:

                      try
                        ...
                      except
                        on EZeroDivide do HandleZeroDivide;
                        on EOverflow do HandleOverflow;
                        on EMathError do HandleMathError;
                      else
                        HandleAllOthers;
                      end;
                      

                      Here, the else clause handles any exception that isn’t an SysUtils.EMathError.

                      An exception block that contains no exception handlers, but instead consists only of a list of statements, handles all exceptions. For example:

                      try
                         ...
                      except
                         HandleException;
                      end;
                      

                      Here, the HandleException routine handles any exception that occurs as a result of executing the statements between try and except.

                      Re-raising Exceptions

                      When the reserved word raise occurs in an exception block without an object reference following it, it raises whatever exception is handled by the block. This allows an exception handler to respond to an error in a limited way and then re-raise the exception. Re-raising is useful when a procedure or function has to clean up after an exception occurs but cannot fully handle the exception.

                      For example, the GetFileList function allocates a TStringList object and fills it with file names matching a specified search path:

                      function GetFileList(const Path: string): TStringList;
                      var
                        I: Integer;
                        SearchRec: TSearchRec;
                      begin
                        Result := TStringList.Create;
                        try
                          I := FindFirst(Path, 0, SearchRec);
                          while I = 0 do
                            begin
                                Result.Add(SearchRec.Name);
                                I := FindNext(SearchRec);
                            end;
                        except
                            Result.Free;
                            raise;
                        end;
                      end;
                      

                      GetFileList creates a TStringList object, then uses the FindFirst and FindNext functions (defined in SysUtils) to initialize it. If the initialization fails — for example because the search path is invalid, or because there is not enough memory to fill in the string list — GetFileList needs to dispose of the new string list, since the caller does not yet know of its existence. For this reason, initialization of the string list is performed in a try…except statement. If an exception occurs, the statement’s exception block disposes of the string list, then re-raises the exception.

                      Nested Exceptions

                      Code executed in an exception handler can itself raise and handle exceptions. As long as these exceptions are also handled within the exception handler, they do not affect the original exception. However, once an exception raised in an exception handler propagates beyond that handler, the original exception is lost. This is illustrated by the Tan function below:

                      type
                         ETrigError = class(EMathError);
                         function Tan(X: Extended): Extended;
                         begin
                            try
                              Result := Sin(X) / Cos(X);
                            except
                              on EMathError do
                              raise ETrigError.Create('Invalid argument to Tan');
                            end;
                         end;
                      

                      If an SysUtils.EMathError exception occurs during execution of Tan, the exception handler raises an ETrigError. Since Tan does not provide a handler for ETrigError, the exception propagates beyond the original exception handler, causing the SysUtils.EMathError exception to be destroyed. To the caller, it appears as if the Tan function has raised an ETrigError exception.

                      Try…finally Statements

                      Sometimes you want to ensure that specific parts of an operation are completed, whether or not the operation is interrupted by an exception. For example, when a routine acquires control of a resource, it is often important that the resource be released, regardless of whether the routine terminates normally. In these situations, you can use a try…finally statement.

                      The following example shows how code that opens and processes a file can ensure that the file is ultimately closed, even if an error occurs during execution:

                      Reset(F);
                      try
                         ... // process file F
                      finally
                         CloseFile(F);
                      end;
                      

                      The syntax of a try…finally statement is

                      try statementList1 finally statementList2 end
                      

                      where each statementList is a sequence of statements delimited by semicolons. The try…finally statement executes the statements in statementList1 (the try clause). If statementList1 finishes without raising exceptions, statementList2 (the finally clause) is executed. If an exception is raised during execution of statementList1, control is transferred to statementList2; once statementList2 finishes executing, the exception is re-raised. If a call to the Exit, Break, or Continue procedure causes control to leave statementList1, statementList2 is automatically executed. Thus the finally clause is always executed, regardless of how the try clause terminates.

                      If an exception is raised but not handled in the finally clause, that exception is propagated out of the try…finally statement, and any exception already raised in the try clause is lost. The finally clause should therefore handle all locally raised exceptions, so as not to disturb propagation of other exceptions.

                      Standard Exception Classes and Routines

                      The SysUtils and System units declare several standard routines for handling exceptions, including ExceptObject, ExceptAddr, and ShowException. SysUtils, System and other units also include dozens of exception classes, all of which (aside from OutlineError) derive from SysUtils.Exception.

                      The SysUtils.Exception class has properties called Message and HelpContext that can be used to pass an error description and a context ID for context-sensitive online documentation. It also defines various constructor methods that allow you to specify the description and context ID in different ways.

                      See Also

                      • Classes and Objects (Delphi)
                      • Fields (Delphi)
                      • Methods (Delphi)
                      • Properties (Delphi)
                      • Nested Type Declarations
                      • Class References
                      • Operator Overloading (Delphi)
                      • Class and Record Helpers (Delphi)
                      • Handling Exceptions
                      Handling errors in Delphi
                      Whilst we all want to spend our time writing functional code, errors will and do occur in code from time to time. Sometimes, these are outside of our control, such as a low memory situation on your PC.

                       
                      In serious code you should handle error situations so that at the very least, the user is informed about the error in your chosen way.

                       
                      Delphi uses the event handling approach to error handling. Errors are (mostly) treated as exceptions, which cause program operation to suspend and jump to the nearest exception handler. If you don’t have one, this will be the Delphi default handler — it will report the error and terminate your program.

                       
                      Often, you will want to handle the error, and continue with your program. For example, you may be trying to display a picture on a page, but cannot find it. So you might display a placeholder instead. Much like Internet Explorer does.

                       
                      Try, except where there are problems
                      Delphi provides a simply construct for wrapping code with exception handling. When an exception occurs in the wrapped code (or anything it calls), the code will jump to the exception handling part of the wrapping code :

                       

                       begin

                         Try

                           ...

                           The code we want to execute

                           ...

                         Except

                           ...

                           This code gets executed if an exception occurs in the above block

                           ...

                         end;

                       end;

                      We literally try to execute some code, which will run except when an error (exception) occurs. Then the except code will take over.

                       
                      Let us look at a simple example where we intentionally divide a number by zero :

                       

                       var

                         number1, number0 : Integer;

                       begin

                         try

                           number0 := 0;

                           number1 := 1;

                           number1 := number1 div number0;

                           ShowMessage('1 / 0 = '+IntToStr(number1));

                         except

                           on E : Exception do

                           begin

                             ShowMessage('Exception class name = '+E.ClassName);

                             ShowMessage('Exception message = '+E.Message);

                           end;

                         end;

                       end;

                      When the division fails, the code jumps to the except block. The first ShowMessage statement therefore does not get executed.

                       
                      In our exception block, we can simpl place code to act regardless of the type of error. Or we can do different things depending on the error. Here, we use the On function to act on the exception type.

                       
                      The On clause checks against one of a number of Exception classes. The top dog is the Exception class, parent of all exception classes. This is guaranteed to be activated above. We can pick out of this class the name of the actual exception class name (EDivByZero) and the message (divide by zero).

                       
                      We could have multiple On clauses for specific errors :

                       

                         except

                          // IO error

                           On E : EInOutError do

                             ShowMessage('IO error : '+E.Message);

                          // Dibision by zero

                           On E : EDivByZero do

                             ShowMessage('Div by zero error : '+E.Message);

                          // Catch other errors

                           Else

                             ShowMessage('Unknown error');

                         end;

                      What happens when debugging
                      Note that when you are debugging your code within Delphi, Delphi will trap exceptions even if you have exception handling. You must then click OK on the error dialogue, then hit F9 or the green arrow to continue to your except clause. You can avoid this by changing the debug options.

                       
                      And finally …
                      Suppose that instead of trapping the error where it occurs, you may want to let a higher level exception handler in your code to do a more global trapping. But your code may have created objects or allocated memory that is now no longer referenced. It is dangerous to leave these allocations lying around.

                       
                      Delphi provides an alternative part to the exception wrapper the Finally clause. Instead of being called when an exception occurs, the finally clause is always called after part or all of the try clause is executed. It allows us to free up allocated memory, or other such activities. However, it does not trap the error — the next highest exception handling (try) block that we are nested in is located and executed.

                       
                      Once you are done debugging the software it is time to relax. Get up out of your modern office furniture and take a nap or go outside. It is important to take breaks from your work and have fun.

                       
                      Raising exceptions
                      We can not only raise exceptions at our own choosing, but we can create Exception classes to manage them. This kind of processing is somewhat beyond the basics, being more appropriate to large applications, especially those using many large modules. These modules may generate their own exception types. Here are the most common exception types :

                       

                       Exception             Base class

                       EAbort                Abort without dialog

                       EAbstractError        Abstract method error

                       AssertionFailed       Assert call failed

                       EBitsError            Boolean array error

                       ECommonCalendarError  Calendar calc error

                         EDateTimeError      DateTime calc error

                         EMonthCalError      Month calc error

                         EConversionError    Raised by Convert

                       EConvertError         Object convert error

                       EDatabaseError        Database error

                       EExternal             Hardware/Windows error

                         EAccessViolation    Access violation

                         EControlC           User abort occured

                         EExternalException  Other Internal error

                       EIntError             Integer calc error

                         EDivByZero          Integer Divide by zero

                         EIntOverflow        Integer overflow

                         ERangeError         Out of value range

                       EMathError            Floating point error

                         EInvalidArgument    Bad argument value

                         EInvalidOp          Inappropriate operation

                         EOverflow           Value too large

                         EUnderflow          Value too small

                         EZeroDivide         Floating Divide by zero

                       EStackOverflow        Severe Delphi problem

                       EHeapException        Dynamic memory problem

                         EInvalidPointer     Bad memory pointer

                         EOutOfMemory        Cannot allocate memory

                       EInOutError           IO error

                       EInvalidCast          Object casting error

                       EInvalidOperation     Bad component op

                       EMenuError            Menu item error

                       EOSError              Operating system error

                       EParserError          Parsing error

                       EPrinter              Printer error

                       EPropertyError        Class property error#

                       EPropReadOnly         Invalid property access

                       EPropWriteOnly        Invalid property access

                       EThread               Thread error

                       EVariantError         Variant problem

                       

                      Исключения и взаимодействие с API

                      На текущий момент мы уже знаем достаточно многое из основ ООП. Однако созда-ние приложений под Windows в среде Delphi не ограничивается применением объ-ектно-ориентированного подхода. В частности, иногда возникают такие ситуации, что приходится обращаться к функциям Windows API напрямую. Кроме того, нам следует рассмотреть обработку ошибок в программах, а так же осветить вопрос некоторых глобальных объектов.

                      Исключения и их классы

                      Исключительные ситуации, или исключения (exception) могут возникать по ходу выполнения программы ввиду целого ряда причин. Они могут быть вызваны как ошибками в коде программы (например, при попытке обратиться к объекту, который не был предварительно создан), при вводе пользователем неожидаемых значений (например, строки, которая не может быть приведена к числу), при ошибках работы оборудования и т.д. Любая программа, претендующая на качественную разработку, должна уметь обрабатывать все подобные исключительные ситуации.

                      При возникновении подобных ошибок в программах, созданных при помощи Delphi, автоматически создается объект — Exception. Класс Exception является базовым для ряда других классов исключений — EMathError, EInvalidOp, EZeroDivide и т.д. (названия всех классов, относящиеся к исключениям, принято начинать не с буквы T, а с буквы E). Он происходит непосредственно от класса TObject и имеет 2 свойства — Message и HelpContext, а так же 8 методов.

                      Свойство Message имеет тип string и содержит описание исключения. При возникновении ошибки этот текст используется в окне сообщения. Ас войство HelpContext определяет индекс раздела справочного файла, содержащего информацию об ошибке. Если значение этого свойства равно нулю, то оно будет ссылаться на раздел справки родительского объекта.

                      Что касается методов, то все они представлены различными вариантами метода Create. Сам метод Create для исключений определен следующим образом:

                      constructor Create(const Msg: string);

                      Т.е., фактически, создавая исключение, следует сразу же назначить значение его свойству Message при помощи аргумента конструктора. Другой вариант конструктора, CreateHelp, позволяет параллельно назначить значение и для второго свойства:

                      constructor CreateHelp(const Msg: string; AHelpContext: Integer);

                      Если в тексте сообщения следует привести какие-либо динамически получаемые данные, то используют вариант конструктора с суффиксом Fmt:

                      constructor CreateFmt(const Msg: string; const Args: array of const);
                      constructor CreateFmtHelp (const Msg: string; const Args: array of const; AHelpContext: Integer);

                      При этом значения, указанные в массиве Args, будут подставлены в строку. Для этого используется функция Format, которой передаются строка и массив в качестве аргументов. Эта функция выполняет подстановку значений массива в места строки, выделенные при помощи символа %. Например, если строка выглядит как «Ошибка в функции %s», а массив определен как «[‘MyFunc’]», то результатом выполнения этой функции будет «Ошибка в функции MyFunc». Соответственно, создание подобного исключения будет выглядеть следующим образом:

                      constructor CreateFmt('Ошибка в функции %s', ['MyFunc']);

                      Как уже было отмечено, класс Exception имеет ряд потомков, каждый из которых предназначен для обработки того или иного типа ошибок. Например, для математических ошибок определен класс EMathError. Однако этот класс сам по себе не используется, зато его потомки, среди которых отметим классы EInvalidOp, EOverflow, EZeroDivide, используются для оповещения о таких ситуациях, как неверная операция, переполнение буфера и попытка деления на 0, соответственно.

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

                      Вызвать исключение в программе можно и искусственным методом — при помощи ключевого слова raise. Например программа может проверять какой-либо ввод пользователя, и в том случае, если он оказывается не тем, что ожидалось, генерировать исключительную ситуацию:

                      if password <> 'password' then raise Exception.Create('Неверный пароль!');

                      Выполнение оператора, указанного после raise, приводит к возникновению исключительной ситуации. После этого дальнейшее выполнение кода процедуры прерывается, равно как и кода, вызвавшего эту процедуру, если вызов был произведен из другой подпрограммы. Перемещение исключения можно рассматривать с точки зрения всплытия, т.е. с места своего возникновения ошибка последовательно «всплывает» сначала к вызвавшей данную процедуру или функцию подпрограмме, от нее — к следующей и т.д., пока не дойдет до уровня выполнения программы, т.е. до глобального объекта Application. На этом, конечном этапе и будет выдано сообщение об ошибке.

                      ПРИМЕЧАНИЕ
                      С некоторыми глобальными объектами, в том числе с Application, мы ознакомимся несколько позже в этой же главе.

                      Если при этом ошибка возникла в основном коде программы (т.е. вызвавший ошибку код был написан в самом файле проекта dpr), то на этом выполнение программы прекратится, о чем будет выдано сообщение (рис. 10.1).

                      Ошибка приложения (исключение Delphi)

                      Рис. 10.1. Ошибка приложения

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

                      Обработка исключений

                      Для обработки исключительных ситуаций в Delphi используются специальные операторы — try…except и try…finally. Эти операторы являются своего рода ловушками для исключительных ситуаций и позволяют разработчику приложения предусмотреть код, обрабатывающий возникшие исключения. Тем самым можно на любом этапе перехватить дальнейшее всплытие ошибки.

                      При помощи оператора try…except выполняет перехват ошибки, как правило, с целью ее подавления. Он имеет следующий синтаксис:

                      try
                      <потенциально вызывающий исключения код>
                      except
                      [ on <Класс исключения> do <оператор>; ]
                      end;

                      В том случае, если между except и end не писать никакого кода, то исключительная ситуация будет просто подавлена. Однако такое подавление чаще всего не является достаточным условием, поскольку оно не несет никакой информации ни пользователю, ни самой программе. Например, если так подавить ошибку с неверным паролем (а из-за подавления никакого сообщения выдано не будет), то пользователь такой программы может лишь догадываться, почему после того, как он сообщил пароль, ничего не происходит. В данном случае было бы правильным все-таки сообщить о том, что пароль введен не верно. Для этого используют вложенную секцию on…do:

                      try
                      if password <> 'password' then raise Exception.Create('Неверный пароль!');
                      except
                      on E: Exception do ShowMessage(E.Message);
                      end;

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

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

                      try
                      if password <> 'password' then raise Exception.Create('Неверный пароль!');
                      except
                      on Exception do ShowMessage('ОШИБКА!');
                      end;

                      Что касается блоков обработки, то их может быть несколько, каждый — для своего класса исключения:

                      try
                      a:=b*c/d;
                      except
                      on EZeroDivide do ShowMessage('Делить на 0 нельзя');
                      on EOverflow do ShowMessage('Слишком большое число');
                      on EMathError do ShowMessage('Математическая ошибка');;
                      end;

                      Здесь мы определили 3 блока, и в случае возникновения той или иной исключительной ситуации, будет выдано то или иное сообщение. Этим данная часть оператора напоминает оператор case, для которого, как мы помним, существовал вариант «для остальных случаев» — else. Имеется такая возможность и здесь:

                      try
                      a:=b*c/d;
                      except
                      on EZeroDivide do ShowMessage('Делить на 0 нельзя');
                      on EOverflow do ShowMessage('Слишком большое число');
                      on EMathError do ShowMessage('Математическая ошибка');
                      else
                      ShowMessage('Общая ошибка');
                      end;

                      Наконец, если тип ошибки не имеет никакого значения, то можно оставить только общий обработчик, для чего не требуется даже ключевого слова else:

                      try
                      a:=b*c/d;
                      except
                      ShowMessage('Общая ошибка');
                      end;
                      Важно лишь отметить, что все эти блоки выполняются только тогда, когда возникает исключительная ситуация. При этом, если после ключевого слова try расположено несколько операторов, и исключение возникает в одном из них, то все последующие выполнены не будут. Вместе с тем, случаются ситуации, когда имеется код, который следует выполнить в любом случае, без оглядки на то, что случится перед этим. В таких случаях используют другой оператор - try…finally, и требующий обязательного выполнения код помещают в часть после finally. Типичным примером использования такой конструкции можно считать уничтожение объектов или иные операции освобождения памяти, а так же закрытия файлов и т.д. Например, при работе с файлами всегда следует использовать try…finally для закрытия файла:
                      try
                      Rewrite(F);
                      writeln(F,s);
                      finally
                      CloseFile(F);
                      end;

                      В данном случае, если даже произойдет ошибка, связанная с доступом к файлу — т.е. если его не удастся открыть (например, если диск защищен от записи), или же записать в него информацию (нет места на диске), закрыт он будет в любом случае, что предотвратит возможные дальнейшие ошибки. При этом само исключение подавлено не будет, т.е. сообщение об ошибке будет выведено и дальнейшее выполнение подпрограммы (но уже после блока finally…end) будет прервано.

                      Но оба подхода можно комбинировать. Например, в данном случае блок try…finally можно вложить в блок try…except:

                      try
                      AssignFile(F);
                      try
                      Rewrite(F);
                      writeln(F,s);
                      finally
                      CloseFile(F);
                      end;
                      except
                      on E: Exception do ShowMessage(E.Message);
                      end;

                      Кроме этого, в Delphi допускается вкладывать однотипные обработчики ошибок друг в друга, например, один блок try…except может быть вложен в другой.

                      Глобальные объекты

                      При создании Windows-приложений нередко возникает необходимость в управлении программой в целом как отдельным объектом. Для этих целей в Delphi предусмотрен специальный объект — Application класса TApplication, представляющий программу в целом. Его использование можно увидеть в любом файле проекта VCL-приложения. Чтобы увидеть это, достаточно создать новое приложение и открыть файл проекта, для откытия которого можно воспользоваться списком модулей, вызываемого кнопкой View Unit (можно так же через главное меню — View ‘ Units, или при помощи сочетания горячих клавиш Ctrl+F12). По умолчанию он имеет название Project1, и его стандартный код имеет вид, приведенный в листинге 10.1.

                      Листинг 10.1. Заготовка кода для VCL-приложения

                      program Project1;
                      uses
                      Forms,
                      Unit1 in 'Unit1.pas' {Form1};
                      {$R *.res}
                      begin
                      Application.Initialize;
                      Application.CreateForm(TForm1, Form1);
                      Application.Run;
                      end.

                      Уже по приведенному в листинге коду мы можем познакомиться с 3 основными методами этого объекта — Initialize, CreateForm и Run. Первый производит подготовительную работу, т.е. фактически, создает объект приложения. Метод CreateForm используется для создания окон приложения, а метод Run производит фактический запуск программы на выполнение. Среди других методов приложения моно отметить такие, как Minimize и Restore, служащие, соответственно, для сворачивания программы на панель задач и для ее восстановления, а так же метод BringToFront, который выводит окно на верхнюю поверхность рабочего стола. Метод Terminate используется для прекращения работы программы (он вызывается автоматически, когда закрывается главное окно приложения). Еще 4 метода — HelpCommand, HelpContext, HelpJump и HelpKeyword — предназначены для работы со справочными файлами.

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

                      Среди свойств приложения, прежде всего, следует отметить такие, как Title, Icon и HelpFile. Свойство Title определяет заголовок программы, т.е. то, что вы видите на панели задач. Свойство Icon определяет значок («иконку») программы. Ну а свойство HelpFile связывает приложение с файлом справочной информации. Все эти свойства можно определить как программно, написав соответствующий код, так и при помощи окна свойств проекта (Project > Options), на закладке Application (рис. 10.2).

                      Установка параметров приложения в окне свойств проекта Delphi

                      Рис. 10.2. Установка параметров приложения в окне свойств проекта

                      Если установить в диалоге Project Options новые значения и нажать на кнопку OK, то внесенные изменения для свойств Title и HelpFile отобразятся в коде программы. Что касается значка программы, то он хранится в отдельном файле ресурсов (res), который присоединяется к приложению в процессе компиляции, для чего используется директива «{$R *.res}».

                      Поскольку любой визуальный компонент может отображать всплывающую текстовую подсказку, то для объекта Application предусмотрен ряд свойств, управляющих видом и выводом таких подсказок. В частности, цвет определяют при помощи свойства HintColor, задержку перед появлением после наведения на компонент мышки — при помощи HintPause, а время его отображения — свойством HintHidePause.

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

                      Помимо Application, при запуске приложения создается еще один глобальный объект, представляющий экранную среду — Screen. При помощи этого объекта можно получить информацию о разрешение экрана, установить вид курсора мыши для приложения, или узнать количество его окон. Основные свойства класса TScreen приведены в таблице 10.1.

                      Таблица 10.1. Основные свойства TScreen

                      Свойство Тип Описание
                      ActiveControl TWinControl Указывает, какой элемент управления в данный момент имеет фокус ввода
                      ActiveForm TForm Указывает, какое окно активно в данный момент
                      Cursor TCursor Определяет вид указателя курсора мышки для приложения
                      Cursors array of HCursor Список всех курсоров, доступных для приложения
                      Fonts TStrings Список названий всех шрифтов, доступных для вывода на экран
                      FormCount Integer Указывает на число окон (форм), созданных приложением
                      Forms array of TForm Список всех окон, созданных приложением
                      Height Integer Указывает на вертикальное разрешение экрана
                      HintFont TFont Определяет шрифт для всплывающих подсказок
                      IconFont TFont Определяет шрифт для подписей к значкам в диалогах выбора файлов
                      MenuFont TFont Определяет шрифт для меню
                      Width Integer Указывает на горизонтальное разрешение экрана
                      WorkAreaHeight Integer Указывает на высоту рабочего стола Windows
                      WorkAreaLeft Integer Указывает на координаты левого угла рабочего стола
                      WorkAreaRect Integer Указывает на координаты прямоугольника, образующего рабочий стол
                      WorkAreaTop Integer Указывает на координаты верхнего угла рабочего стола
                      WorkAreaWidth Integer Указывает на ширину рабочего стола

                      Использовать объекты Screen и Application можно как в главном модуле программы (файле проекта), так и в модулях отдельных форм. При использовании в главном мо-дуле обычно устанавливают глобальные параметры, например, вид всплывающих подсказок. В частности, можно определить довольно-таки экзотический вид всплы-вающих подсказок, дополнив программу следующими строками:

                      Screen.HintFont.Color:=$00408080; // цвет шрифта
                      Screen.HintFont.Size:=14; // размер шрифта
                      Application.HintColor:=$0080FF80; // цвет фона
                      Application.HintPause:=1000; // задержка перед появлением 1 секунда
                      Application.HintHidePause:=2000; // время показа 2 секунды

                      Если вставить этот код в dpr-файл перед обращением к методу Application.Run, то можно будет убедиться, что через секунду после наведения курсора на окно запу-щенного приложения будет появляться всплывающая подсказка с крупным коричне-вым текстом на зеленом фоне. Разумеется, при этом для окна приложения следует установить значения свойства ShowHint в true, и написать какой-либо текст для свой-ства Hint. Впрочем, это можно сделать не только через инспектор объекта в процессе разработки приложения, но и программно, поместив соответствующий код после создания формы. В результате мы получим код, приведенный в листинге 10.2.

                      Листинг 10.2. Использование объектов Application и Screen

                      program app_scr;
                      uses
                      Forms,
                      Unit1 in 'Unit1.pas' {Form1};
                      {$R *.res}
                      begin
                      Application.Initialize;
                      Application.CreateForm(TForm1, Form1);
                      Form1.Hint:='Ну и подсказочка!';
                      Form1.ShowHint:=true;
                      Screen.HintFont.Color:=$00408080;
                      Screen.HintFont.Size:=14;
                      Application.HintColor:=$0080FF80;
                      Application.HintPause:=1000;
                      Application.HintHidePause:=2000;
                      Application.Run;
                      end.

                      Здесь же можно установить и такие параметры, как заголовок программы, используя свойство Title объекта Application:

                      Application.Title:='Super Hint!';

                      Кроме того, можно поэкспериментировать с такими свойствами объекта Screen, как Height и WorkAreaHeight, причем для вывода информации можно использовать заго-ловок главного окна:

                      Form1.Caption:='Экран '+IntToStr(Screen.Height)+', рабочий стол '+ IntToStr(Screen.WorkAreaHeight);

                      В данном случае в строку Uses потребуется дописать модуль SysUtils, поскольку ис-пользованная здесь функция IntToStr расположена именно в этом модуле. Оконча-тельный вариант программы можно найти в каталоге DemoPart2Global.

                      Работа с INI-файлами

                      При разработке приложений часто встает вопрос о том, где и как хранить информа-цию, связанную с его настройками. Нередко для этих целей используются специаль-ные INI-файлы, которые хранят в себе информацию, разбитую по логическим груп-пам в виде «ключ-значение». В Delphi имеется класс, обеспечивающий простую ра-боту с такими файлами — TIniFile. Чтобы приложение могло получить доступ к этому классу, в секцию используемых модулей следует добавить inifiles.

                      Имя файла, ассоциированного с объектом типа TIniFile, задается непосредственно при создании экземпляра этого класса, в конструкторе Create:

                      var MyIni: TIniFile;
                      ...
                      TIniFile.Create('myfile.ini');

                      Впоследствии можно узнать, какой файл ассоциирован с данным объектом при по-мощи его свойства FileName, однако изменить его уже не получится. Вместе с тем, у TIniFile имеется свыше 20 методов, при помощи которых можно считывать, прове-рять и изменять содержимое INI-файла. Все они приведены в таблице 10.2.

                      Таблица 10.2. Методы класса TIniFile

                      Метод Принимаемые параметры Описание
                      DeleteKey const Section, Ident: String Удаляет указанный ключ из INI файла
                      EraseSection const Section: String Удаляет содержимое указанной секции в INI файле
                      ReadSection const Section: String; Strings: TStrings Считывает имена всех ключей в указанной секции и заносит их в список строк
                      ReadSections Strings: TStrings Считывает названия всех секций в файле и заносит их в список строк
                      ReadSectionValues const Section: String; Strings: TStrings Считывает все значения в указанной секции и заносит их в список строк
                      ReadString const Section, Ident, Default: String Считывает и возвращает значение-строку из указанного ключа
                      WriteString const Section, Ident, Value: String Записывает значение-строку в указанный ключ
                      ReadBool const Section, Ident: String; Default: Boolean Считывает и возвращает булево значение из указанного ключа
                      ReadDate const Section, Ident: String; Default: TDateTime Считывает и возвращает значение-дату из указанного ключа
                      ReadDateTime const Section, Ident: String; Default: TDateTime Считывает и возвращает значение-дату и время из указанного ключа
                      ReadFloat const Section, Ident: String; Default: Double Считывает и возвращает значение-вещественное число из указанного ключа
                      ReadInteger const Section, Ident: String; Default: Longint Считывает и возвращает значение-целое число из указанного ключа
                      ReadTime const Section, Ident: String; Default: TDateTime Считывает и возвращает значение-время из указанного ключа
                      SectionExists const Section: String Проверяет INI файл на наличие указанной секции
                      WriteBool const Section, Ident: String; Value: Boolean Записывает булево значение в указанный ключ
                      WriteDate const Section, Ident: String; Value: TDateTime Записывает значение-дату в указанный ключ
                      WriteDateTime const Section, Ident: String; Value: TDateTime Записывает значение-дату и время в указанный ключ
                      WriteFloat const Section, Ident: String; Value: Double Записывает значение-вещественное число в указанный ключ
                      WriteInteger const Section, Ident: String; Value: Longint Записывает значение-целое в указанный ключ
                      WriteTime const Section, Ident: String; Value: TDateTime Записывает значение-время в указанный ключ
                      ValueExists const Section, Ident: String Проверяет INI файл на наличие указанного ключа в определенной секции

                      Таким образом, можно без каких-либо дополнительных накладных расходов (с точки зрения написания собственного кода), создавать и считывать стандартные INI-файлы. Например, мы можем создать приложение, которое сможет «запоминать» введенную информацию и отображать ее при следующем запуске. В принципе, мы уже делали нечто подобное еще при создании программы «угадывания чисел», рассмотренной в первой части. Однако тогда мы лишь последовательно записывали в файл пару строк, а затем таким же образом их считывали. Но если бы нам требовалось сохранить большее количество значений, то мы столкнулись бы с трудностями такого рода, как невозможность идентифицировать то или иное значение при просмотре файла. Кроме того, пришлось бы постоянно держать в уме, какая по строка что должна хранить. Использование INI-файлов решает эту задачу.

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

                      Листинг 10.3. Название листинга

                      program myini;
                      {$APPTYPE CONSOLE}
                      uses
                      SysUtils, IniFiles;
                      var
                      ans: Char;
                      fn: string;
                      begin
                      write('Load data from an INI file? [Y/N]');
                      readln(ans);
                      if (ans='Y') or (ans='y') then begin
                      write('Please input file name: ');
                      readln(fn);
                      fn:='c:'+fn+'.ini';
                      if FileExists(fn) then begin
                      ShowData(fn);
                      end else begin
                      writeln('File not found and will be created.');
                      FillData(fn);
                      end;
                      end else begin
                      write('Please input file name to save data: ');
                      readln(fn);
                      fn:='c:'+fn+'.ini';
                      FillData(fn);
                      end;
                      readln(fn);
                      end.

                      Прежде всего, наша программа интересуется, хочет ли пользователь просмотреть информацию из уже существующего файла, или нет, и если хочет, то запрашивает имя файла. Здесь мы подразумеваем, что пользователь будет вводить только имя файла, без пути и расширения, которые добавляются автоматически. Затем стандартная функция FileExists проверяет получившийся файл на существование, после чего либо выводит его содержимое при помощи процедуры ShowDate (которую нам еще предстоит создать), либо выводит сообщение о том, что файл не найден, но будет создан. После этого программа обращается к процедуре FillData, которая так же будет нами написана для ввода информации и сохранения ее в INI-файле. Эта же функция будет вызвана и в том случае, если пользователь изначально откажется от вывода информации, в таком случае программа предварительно запросит имя файла для дальнейшего сохранения.

                      Теперь, когда основа программы готова, можно определиться, какие данные мы хотим хранить, и какой для этого понадобится формат файла. Допустим, мы хотим сохранить информацию 2-х категорий: персональную и рабочую. В таком случае наш INI файл будет состоять из 2 секций, скажем, Userdata и Jobdata. В первой секции сохраним имя (Name) и возраст (Age), а во второй — должность (Title) и оклад (Salary). Процедура, отвечающая за вывод информации, получится достаточно простой — в ней достаточно создать INI-файл с указанным именем и последовательно считывать информацию, попутно выводя ее на экран. Например, для строкового значения мы получим следующий код:

                      writeln('Name...... '+IniF.ReadString('Userdata','Name','Anonymous'));

                      Если же речь идет о числовом значении, то нам придется предварительно преобразовать его в строку:

                      writeln('Age....... '+IntToStr(IniF.ReadInteger('Userdata','Age',0)));

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

                      write('Name: ');
                      readln(s);
                      IniF.WriteString('Userdata','Name',s);

                      Подобный код потребуется выполнить для каждого поля данных, при этом нам понадобятся 3 различных переменных для хранения данных 3 типов (дважды — строк, и по разу целое и вещественное числа). Предварительно следует не забыть создать переменную типа TIniFile, и вывести пояснительный текст, а к завершению работы процедуры освободить память, занимаемую более не нужной переменной. Последнее условие следует выполнить и в процедуре ShowData. В итоге мы получим код, приведенный в листинге 10.4.

                      Листинг 10.4. Процедуры сохранения и считывания INI-файлов

                      procedure FillData(fn: string);
                      var
                      IniF: TIniFile;
                      s: string;
                      i: integer;
                      f: double;
                      begin
                      IniF:=TIniFile.Create(fn);
                      writeln('Please fill a form...');
                      write('Name: ');
                      readln(s);
                      IniF.WriteString('Userdata','Name',s);
                      write('Age: ');
                      readln(i);
                      IniF.WriteInteger('Userdata','Age',i);
                      write('Position: ');
                      readln(s);
                      IniF.WriteString('Jobdata','Title',s);
                      write('Salary: ');
                      readln(f);
                      IniF.WriteFloat('Jobdata','Salary',f);
                      IniF.Free;
                      end;
                      procedure ShowData(fn: string);
                      var
                      IniF: TIniFile;
                      begin
                      IniF:=TIniFile.Create(fn);
                      writeln('Name...... '+IniF.ReadString('Userdata','Name','Anonymous'));
                      writeln('Age....... '+IntToStr(IniF.ReadInteger('Userdata','Age',0)));
                      writeln('Position.. '+IniF.ReadString('Jobdata','Title','Unemployed'));
                      writeln('Salary.... '+FloatToStrF(IniF.ReadFloat('Jobdata','Salary',0.00),ffFixed,6,2));
                      IniF.Free;
                      end;

                      С полным исходным кодом программы можно ознакомиться в примере, расположенном в каталоге DemoPart2IniFiles.

                      Работа с реестром Windows

                      Файлы INI и класс TIniFiles — достаточно удобный способ хранения различной настроечной информации. Тем не менее, начиная с Windows 95, появилось централизованное хранилище для настроек системы и всех установленных программ — реестр (Registry). При разработке приложений в Delphi удобнее всего работать с реестром, используя класс TRegistry. Чтобы включить объявление этого класса, следует указать модуль registry в списке uses.

                      Реестр Windows имеет несколько ключевых разделов, в чем можно убедиться, открыв имеющуюся в Windows программу редактирования реестра (regedit). В частности это разделы HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_USERS, HKEY_LOCAL_MACHINE и HKEY_CURRENT_CONFIG. Чтобы приступить к работе с реестром из программы, требуется указать один из разделов. Делается это при помощи свойства RootKey:

                      var Reg: TRegistry;
                      ...
                      Reg:=TRegistry.Create;
                      Reg.RootKey:=HKEY_CURRENT_USER;

                      Далее в ход идут методы класса TRegistry. В частности, за выбор раздела реестра, из которого надо будет считывать данные, используется метод OpenKeyReadOnly. В качестве аргумента ему передается адрес раздела реестра, например:

                      Reg.OpenKeyReadOnly('SOFTWAREMySoftTestApp');

                      Если указанный раздел существует, и к нему может быть обеспечен доступ, то обращение к данному методу вернет истину. Если же раздела может не существовать, или если требуется открыть раздел на запись, то используют метод OpenKey:

                      Reg.OpenKey('SOFTWAREMySoftTestApp',true);

                      Для него в качестве 2-го параметра указывают булево значение, которое указывает на то, должен ли указанный раздел быть создан, если его не существует. В результате выполнения приведенного кода раздел, при необходимости, будет создан и открыть на чтение и запись. Если же требуется только создать новый раздел, то используют метод CreateKey:

                      Reg.CreateKey('SOFTWAREMySoftTestApp');

                      Для удаления раздела используют метод DeleteKey, а для проверки указанного раздела на существование — KeyExists. Подобно методу CreateKey, эти методы так же принимают адрес раздела и возвращают ложь или истину в зависимости от результата операции.

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

                      Что касается записи и считывания значений, то, подобно классу TIniFile, для TRegistry определен ряд методов для взаимодействия с данными различных типов, причем для реестра к типам Boolean, String, Double, Integer и даты-времени, добавляется еще и Currency. Соответственно, мы имеем 8 пар методов для этих целей.

                      Для примера рассмотрим приложение, состоящее из единственного окна, которое будет «запоминать» свои размеры и расположение на экране. Для этого создадим новое VCL-приложение (File ‘ New ‘ Application), щелкнем сначала по его форме (Form1), а затем — по окну инспектора объекта (Object Inspector). В нем выберем закладку Events (события), найдем событие OnClose и дважды щелкнем по строке напротив. В результате мы получим заготовку для процедуры TForm1.FormClose, в которую нам надо будет добавить объявление переменной для реестра:

                      var
                      Reg: TRegistry;
                      Затем в теле функции напишем следующие строки:
                      Reg:=TRegistry.Create;
                      Reg.RootKey:=HKEY_CURRENT_USER;
                      Reg.OpenKey('SOFTWAREMySoftTestApp',true);
                      Reg.WriteInteger('left',Form1.Left);
                      Reg.WriteInteger('top',Form1.Top);
                      Reg.WriteInteger('height',Form1.Height);
                      Reg.WriteInteger('width',Form1.Width);
                      Reg.Free;

                      Вначале мы создаем экземпляр класса, затем выбираем корневой раздел, после чего открываем ключ на запись (он будет создан при необходимости), и последовательно заносим в него пространственные координаты окна. В завершение работы этой процедуры мы экземпляр класса удаляется из памяти за ненадобностью.

                      Теперь рассмотрим считывание из реестра, для чего создадим процедуру, обрабатывающую событие создания окна, для чего в инспекторе объекта найдем событие OnCreate и сделаем двойной щелчок напротив него. В получившейся процедуре нам так же понадобится сначала объявить переменную Reg, затем создать экземпляр класса и установить корневой раздел. Затем следует открыть раздел на чтение, причем если это окажется невозможным (а при первом запуске так и будет, поскольку раздел будет создан только после выхода из программы), то считывать ничего не потребуется. Поэтому задействуем условный оператор:

                      if Reg.OpenKeyReadOnly('SOFTWAREMySoftTestApp') then begin
                      ...
                      end;

                      После этого остается считать все нужные данные из реестра, присваивая хранящиеся в них значения соответствующим свойствам Form1. Например, для высоты и ширины мы получим:

                      Form1.Height:=Reg.ReadInteger('height');
                      Form1.Width:=Reg.ReadInteger('width');

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

                      if Reg.ValueExists('width') then Form1.Width:=Reg.ReadInteger('width');

                      В результате код этого модуля программы получит приблизительно такой вид, как показано в листинге 10.5.

                      Листинг 10.5. Сохранение координат и размеров окна в реестре

                      unit Unit1;
                      interface
                      uses
                      Windows, Forms, Registry;
                      type
                      TForm1 = class(TForm)
                      procedure FormCreate(Sender: TObject);
                      procedure FormClose(Sender: TObject; var Action: TCloseAction);
                      end;
                      var
                      Form1: TForm1;
                      implementation
                      {$R *.dfm}
                      procedure TForm1.FormCreate(Sender: TObject);
                      var
                      Reg: TRegistry;
                      begin
                      Reg:=TRegistry.Create;
                      Reg.RootKey:=HKEY_CURRENT_USER;
                      if Reg.OpenKeyReadOnly('SOFTWAREMySoftTestApp') then begin
                      if Reg.ValueExists('left') then
                      Form1.Left:=Reg.ReadInteger('left');
                      if Reg.ValueExists('top') then
                      Form1.Top:=Reg.ReadInteger('top');
                      if Reg.ValueExists('height') then
                      Form1.Height:=Reg.ReadInteger('height');
                      if Reg.ValueExists('width') then
                      Form1.Width:=Reg.ReadInteger('width');
                      end;
                      Reg.Free;
                      end;
                      procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
                      var
                      Reg: TRegistry;
                      begin
                      Reg:=TRegistry.Create;
                      Reg.RootKey:=HKEY_CURRENT_USER;
                      Reg.OpenKey('SOFTWAREMySoftTestApp',true);
                      Reg.WriteInteger('left',Form1.Left);
                      Reg.WriteInteger('top',Form1.Top);
                      Reg.WriteInteger('height',Form1.Height);
                      Reg.WriteInteger('width',Form1.Width);
                      Reg.Free;
                      end;
                      end.

                      С исходным кодом приложения так же можно ознакомится, посмотрев его в каталоге DemoPart2Registry.

                      Процедуры и функции стандартных диалогов

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

                      Простейшим вариантом вывода сообщения является использование процедуры ShowMessage. Она отображает переданную ей в качестве аргумента строку на простом диалоговом окне с единственной кнопкой OK. Типичный пример использования этой процедуры — информирование пользователя о выполнении той или иной части программы:

                      ShowMessage('Формат диска C: завершен');

                      Кроме самой процедуры ShowMessage, имеются 2 других варианта — ShowMessagePos и ShowMessageFmt. Первый позволяет вывести диалоговое окно в определенном месте, что достигается путем указания координат по горизонтали и вертикали:

                      ShowMessagePos('Формат диска C: завершен',100,200);

                      Второй позволяет вывести отформатированную строку, используя обращение к функции Format, как и в случае с конструктором исключений. Таким образом, для вывода сообщения с переменной частью предпочтительно использовать именно этот вариант процедуры:

                      ShowMessageFmt('Формат диска %s завершен',['C:']);

                      Все варианты процедуры ShowMessage выводят окно с единственной кнопкой OK, при этом, разумеется, никакого значения не возвращается. В том же случае, если сообщение выводится для того, чтобы запросить у пользователя подтверждения на то или иное действие, то нам, во-первых, потребуется функция — чтобы получить вариант ответа, а так же возможность указать возможные варианты. Все это мы имеем в лице функции MessageDlg, которая имеет следующее определение:

                      function MessageDlg(const Msg: string; DlgType: TMsgDlgType; Buttons: TMsgDlgButtons; HelpCtx: Longint): Word;

                      Здесь сразу же требуется прояснить 2 момента: тип диалога и тип кнопок. За тип диалога отвкечает 2-й параметр, который имеет тип TMsgDlgType и может принимать одно из следующих значений:

                      • mtWarning — диалог типа «предупреждение», имеет заголовок «Warning» и рисунок, изображающий восклицательный знак на фоне желтого треугольника;
                      • mtError — диалог типа «ошибка», имеет заголовок «Error» и изображение косого креста в красном круге;
                      • mtInformation — диалог типа «информация», имеет заголовок «Information» и значок со стилизованной буквой «i» в синих тонах;
                      • mtConfirmation — диалог типа «подтверждение», имеет заголовок «Confirm» и рисунок с зеленым вопросительным знаком;
                      • mtCustom — диалог произвольного типа, имеет заголовок, соответствующий имени выполняемого файла и не содержит изображения.

                      ПРИМЕЧАНИЕ
                      Внешний вид изображений, символизирующих диалог того или иного типа, периодически претерпевает некоторые изменения, в зависимости от версии Delphi.

                      Следующий параметр, имеющий перечисляемый тип TMsgDlgButtons, позволяет указать, какие кнопки должны быть расположены на диалоговом окне. Всего предусмотрено 11 вариантов кнопок, среди них предусмотрены такие, как OK, Cancel, Yes, No и т.д. При этом каждая такая кнопка (кроме Help), будучи нажатой пользователем, закрывает окно, а функция возвращает значение, соответствующее нажатой кнопке. Все варианты кнопок и возвращаемые ими значения, приведены в таблице 10.3.

                      Таблица 10.3. Варианты кнопок и значения, возвращаемые при их нажатии

                      Значение Описание Возвращаемый результат
                      mbYes Кнопка с надписью «Yes» (да) mrYes
                      mbNo Кнопка с надписью «No» (нет) mrNo
                      mbOK Кнопка с надписью «OK» mrOk
                      mbCancel Кнопка с надписью «Cancel» (отмена) mrCancel
                      mbAbort Кнопка с надписью «Abort» (прервать) mrAbort
                      mbRetry Кнопка с надписью «Retry» (повторить) mrRetry
                      mbIgnore Кнопка с надписью «Ignore» (игнорировать) meIgnore
                      mbAll Кнопка с надписью «All» (все) mrAll
                      mbNoToAll Кнопка с надписью «No to All» (нет для всех) mrNoToAll
                      mbYesToAll Кнопка с надписью «Yes to All» (да для всех) mrYesToAll
                      mbHelp Кнопка с надписью «Help» (справка)

                      Следует оговориться, что все возвращаемые значения, на самом деле, являются целыми числами, что видно по определению функции. Но поскольку запомнить, что, к примеру, возвращаемое значение для OK — это 1, а для Yes — 6, весьма проблематично, то на практике вместо них используются константы, которые как раз и были приведены в таблице 10.3.

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

                      MessageDlg('Ошибка чтения с диска. Продолжить?', mtError, [mbRetry, mbAbort], 0);

                      Поскольку эта функция возвращает то или иное значение, то ее использование часто сопровождается условным оператором:

                      if MessageDlg('Форматировать диск C:?',mtConfirmation,[mbYes,mbNo],0) = mrYes then FormatDriveCProc();

                      Другой вариант, для случая с множественными вариантами ответа — использование совместно с оператором-переключателем:

                      case MessageDlg('Файл изменен. Сохранить перед выходом?', mtWarning, [mbYes, mbNo, mbCancel], 0) of
                      mrYes: begin SaveFileProc(); Close; end;
                      mrNo: Close;
                      mrCancel: exit;
                      end;

                      Подобно процедуре ShowMessage, для функции MessageDlg так же предусмотрен вариант с позиционированным выводом окна. Такой вариант этой функции называется MessageDlgPos. Ее отличие от MessageDlg состоит в том, что к списку аргументов добавлено еще 2 параметра, отвечающих за расположение окна. Такой вариант используется, например, при поиске с заменой в текстовых редакторах:

                      MessageDlgPos('Заменить это вхождение?', mtConfirmation, [mbYes, mbNo], 0, X, Y);

                      Все рассмотренные нами подпрограммы применяются для вывода сообщений. Что касается ввода, то для этих целей, как уже отмечалось, используют функции InputBox и InputQuery. Обе они выводят окно, позволяющее пользователю ввести какое-либо значение — число или строку. Различие между ними состоит лишь в том, что InputBox возвращает непосредственно результат (в виде строки), а InputQuery — истину или ложь, в зависимости от того, нажмет пользователь OK или Cancel. При этом само значение возвращается в качестве одного из параметров. В итоге мы имеем следующий синтаксис для этих функций:

                      function InputBox(const ACaption, APrompt, ADefault: string): string;
                      function InputQuery(const ACaption, APrompt: string; var Value: string): Boolean;

                      Таким образом, какую из функций лучше использовать в данный момент, зависит от контекста применения. Например, если надо просто получить какое-либо значение от пользователя, то можно использовать функцию InputBox:

                      UserName := InputBox('Запрос','Введите ваше имя','анонимно');

                      В данном случае последний параметр функции будет использован в качестве значения по умолчанию (рис. 10.3).

                      Диалоговое окно функции InputBox

                      Рис. 10.3. Диалоговое окно функции InputBox

                      Если же в зависимости от того, введет или нет пользователь новое значение, должна быть выполнена та или иная ветвь алгоритма, то предпочтительнее использовать функцию InputQurey:

                      if InputQurey('Курс доллара ','Введите новый курс',NewCur) then UpdatePrc();

                      Помимо приведенных здесь процедур и функций, в VCL имеется ряд иных подпрограмм, использующих диалоговые окна, включая такие, как диалог выбора каталога или файла. Но поскольку их использование сопряжено с некоторыми неудобствами, в частности, им приходится передавать большое число параметров, то на практике для тех же целей чаще используют компоненты. Например, функция PromptForFileName используется для вывода диалога сохранения или открытия файла. Но более типичным (и удобным!) вариантом обращения к таким диалогам является использование таких стандартных компонент VCL, как TOpenDialog и TSaveDialog, с которыми мы познакомимся в следующей части этой книги.

                      Обработка сообщений и Windows API

                      Как ни широк охват VCL, иногда все-таки возникает потребность в обращении к функциям Windows напрямую. Например, для того же самого вывода окна с текстовым сообщением можно использовать собственную функцию Windows API — MessageBox:

                      MessageBox(0, 'Текст сообщения', 'Заголовок', MB_OK);

                      Необходимость использования функций Windows API может быть вызвана, например, соображениями компактности исполняемого файла: использование диалогов Delphi автоматически подразумевает использование целого рада модулей, необходимых для оконного интерфейса. Если же в самой программе такие модули (например, forms) не задействуются, то их включение в исполняемый код только ради диалога не является хорошей идеей.

                      В то же время, обращение к функциям Windows API может быть вызвано, например, необходимостью перехвата непредусмотренных в Delphi сообщений.

                      ПРИМЕЧАНИЕ
                      Еще одной темой, важной для дальнейшего изучения программирования в Windows вообще и в среде Delphi в частности, является концепция событийного программирования. Дело в том, что хотя ОС Windows, в отличие от Delphi, и не является объектной средой, подход к организации взаимодействия приложений (как с пользователем, так и с системой), основан на одном и том же, а именно — на событиях.

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

                      Для отправки сообщений чаще всего используют функции SendMessage и PostMessage. Обе они выполняют отправку сообщения конкретному окну, разница заключается лишь в том, что SendMessage ожидает ответа от получившего сообщение обработчика, а PostMessage возвращает ответ немедленно. Ценность этих функций состоит в том, что в отличие от средств, предоставляемых VCL, они могут взаимодействовать не только в рамках одного приложения, но и между совершенно разными программами и даже устройствами.

                      Хотя детальное ознакомления с работой Windows API явно не вписывается в рамки данной книги (не забываем, что Delphi была создана как раз для того, чтобы скрыть сложную и неуклюжую API Windows), отметим все-таки некоторые связанные с ней аспекты. Прежде всего, это касается типов данных. Хотя ранние версии Windows были написаны на Pascal, со временем Microsoft перешла на использование C и C++, поэтому типы данных в представлении Windows несколько отличаются от таковых в Delphi. Прежде всего, это касается строк: при работе с Windows напрямую следует использовать не обычные, а C-строки. В Object Pascal для этого предусмотрен специальный тип данных — PChar, а так же функции для преобразования строк одного вида в другой. Так, для преобразования Pascal-строк в C-строки используют функцию StrPCopy, а для обратного преобразования — функцию StrPas.

                      var
                      a: PChar;
                      s: string;
                      ...
                      s:='Строка';
                      new(a); // для С-строк следует предварительно выделять память
                      StrPCopy(a,s); // содержимое Pascal-строки s скопировано в C-строку a
                      s:=StrPas(a);

                      Другие типы данных, часто используемые при работе с API — целые числа, булевы значения и указатели. В таких случаях можно использовать стандартные для Object Pascal типы данных, а к нужному виду они, при необходимости, будут приводиться автоматически.

                      СОВЕТ
                      В поставку Delphi включена документация по Windows API. Ссылки на файлы вы найдете в разделе MS SDK Help Files, вложенном в раздел Help программной группы Delphi в меню кнопки пуск. Наибольший интерес с точки зрения изучения функций API представляет собой файл Win32 Programmer’s reference.

                      Что касается VCL, то в Delphi все же имеется специальный компонент, который может отлавливать все сообщения, адресуемые приложению. Для этого существует компонент AppEvents, который принимает все сообщения, адресованные объекту Application. Среди событий, отслеживаемых компонентом AppEvents, выделим OnMessage — именно это событие происходит, когда приложение получает сообщение от Windows или иной программы. Кроме того, ряд компонент, на самом деле, являются оболочкой для вызова тех или иных функций Windows. Впрочем, о компонентах Delphi будет рассказано в следующей части этой книги.

                      « Черчение, рисование и печать
                      |
                      Работа с VCL в среде Delphi »

                      Техподдержка / Связаться с нами
                      Copyright © 1999-2020 SNK. Все права защищены.
                      При использовании материалов с сайта ссылка на источник обязательна.
                      Рейтинг@Mail.ru

                      Понравилась статья? Поделить с друзьями:
                    • Delphi ошибка unable to create process
                    • Delphi ошибка e2029
                    • Delphi не выводить сообщение об ошибке
                    • Delphi генерация ошибки
                    • Delphi вывод ошибок