Rambler's Top100
"Knowledge itself is power"
F.Bacon
Поиск | Карта сайта | Помощь | О проекте | ТТХ  
 Подземелье Магов
  
 

Фильтр по датам

 
 К н и г и
 
Книжная полка
 
 
Библиотека
 
  
  
 


Поиск
 
Поиск по КС
Поиск в статьях
Яndex© + Google©
Поиск книг

 
  
Тематический каталог
Все манускрипты

 
  
Карта VCL
ОШИБКИ
Сообщения системы

 
Форумы
 
Круглый стол
Новые вопросы

 
  
Базарная площадь
Городская площадь

 
   
С Л С

 
Летопись
 
Королевские Хроники
Рыцарский Зал
Глас народа!

 
  
ТТХ
Конкурсы
Королевская клюква

 
Разделы
 
Hello, World!
Лицей

Квинтана

 
  
Сокровищница
Подземелье Магов
Подводные камни
Свитки

 
  
Школа ОБЕРОНА

 
  
Арсенальная башня
Фолианты
Полигон

 
  
Книга Песка
Дальние земли

 
  
АРХИВЫ

 
 

Сейчас на сайте присутствуют:
 
  
 
Во Флориде и в Королевстве сейчас  14:22[Войти] | [Зарегистрироваться]

Генерация и обработка исключений без подключения SysUtils

Набережных Сергей
дата публикации 25-08-2003 15:10

Генерация и обработка исключений без подключения SysUtils

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

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

Реализация механизма в System сама по себе представляет немалый интерес, но ее рассмотрение выходит за рамки данной статьи. Нас интересует только небольшая ее часть, а именно процедура _ExceptionHandler. Это обработчик исключений, установленный при старте приложения, и получающий управление при генерации системой исключения – например, при вызове приложением функции RaiseException. _ExceptionHandler проводит ряд проверок, в зависимости от которых предпринимаются различные действия. Кратко рассмотрим только некоторые из них:

  1. Если это исключение сгенерировано Delphi-программой, то происходит переход к пункту 5.
  2. Если значение переменной ExceptObjProc не равно nil, то вызывается функция, адрес которой находится в этой переменной, иначе переход к пункту 4.
  3. Если вызванной в п. 2 функции удалось “подобрать” соответствующий класс исключений, то происходит переход к пункту 5.
  4. Так как исключение осталось “неопознанным”, происходит нотификация пользователя и аварийное завершение процесса.
  5. Если значение переменной ExceptProc не равно nil, то вызывается процедура, адрес которой находится в этой переменной, иначе переход к пункту 4.

Таким образом, нас интересуют две переменные: ExceptObjProc и ExceptProc. Заглянем в SysUtils, чтобы посмотреть, как они используются в нем. В секции инициализации этот модуль присваивает им адреса функции GetExceptionObject и процедуры ExceptHandler соответственно. Первая из них пытается подобрать по коду ошибки соответствующий класс исключения и, при удаче, возвращает его экземпляр. Вторая производит нотификацию пользователя, используя строку сообщения из объекта, и вызывает Halt с кодом 1.

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

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

  interface
   
   uses
     Windows;
   
    type
     TLogHandler = procedure (ExceptObject: TObject; ExceptAddr: Pointer);
   
     LException = class
     private
       FExceptAddress: Pointer;
     protected
       function GetExceptionMessage: string; virtual; abstract;
       function GetExceptionTitle: string; virtual;
       property ExceptionAddress: Pointer read FExceptAddress;
       procedure ShowException; virtual;
     public
       property ExceptionMessage: string read GetExceptionMessage;
       property ExceptionTitle: string read GetExceptionTitle;
       function GetAddrString: string;
     end;
   
   var
     LogHandler: TLogHandler = nil;
   
    implementation
   
   { LException }
   
   function LException.GetAddrString: string;
   const
     CharBuf: array[0..15] of Char = '0123456789ABCDEF';
   var
     BufLen: integer;
     Value: Cardinal;
   begin
     BufLen:=Succ(SizeOf(FExceptAddress) shl 1);
     SetLength(Result, BufLen);
     Result[1]:='$';
     Value:=Cardinal(FExceptAddress);
     while BufLen > 1 do
     begin
       Result[BufLen]:=CharBuf[Value and $F];
       Value:=Value shr 4;
       Dec(BufLen);
     end;
   end;
   
   function LException.GetExceptionTitle: string;
   begin
     Result:='Error';
   end;
   
   procedure LException.ShowException;
   begin
     MessageBox(0, PChar(ExceptionMessage), PChar(ExceptionTitle), MB_ICONERROR or MB_TASKMODAL);
   end;

Раз уж мы внедряемся в обработку исключений, то почему бы не предусмотреть заодно и механизм ведения лога ошибок? Для этого и предусмотрен тип TLogHandler и переменная LogHandler. Остальной код прост и вряд ли нуждается в комментариях.

Далее, нам необходимо описать наш обработчик и присвоить его адрес переменной:

  type
     TExceptHandler = TLogHandler;
   
   var
     OldHandler: TExceptHandler;
   
   procedure ExceptHandler(ExceptObject: TObject; ExceptAddr: Pointer);
   begin
     if Assigned(LogHandler) then
     try
       LogHandler(ExceptObject, ExceptAddr);
     except
     end;
     if ExceptObject is LException then
     begin
       LException(ExceptObject).FExceptAddress:=ExceptAddr;
       LException(ExceptObject).ShowException;
       Halt(1);
     end
     else
       if Assigned(OldHandler) then OldHandler(ExceptObject, ExceptAddr);
   end;
   
   procedure InitProc;
   begin
     OldHandler:=TExceptHandler(ExceptProc);
     ExceptProc:=@ExceptHandler;
   end;
   
   procedure FinalProc;
   begin
     TExceptHandler(ExceptProc):=OldHandler;
   end;
   
   initialization
     InitProc;
   
   finalization
     FinalProc;

Как видите, этот код не сложнее предыдущего. Прежде всего, мы вызываем обработчик логов, подстраховавшись от возможных ошибок блоком try-except. На всякий случай, все-таки ведение логов – не то место, где позволительно допускать ошибки. Далее мы проверяем, является ли объект исключения “нашим”, то есть потомком класса LException. Если это так, мы вызываем его методы и завершаем программу вызовом Halt. В противном случае мы вызываем предыдущий в цепочке обработчик. В секции инициализации мы устанавливаем свой обработчик, сохранив адрес предыдущего, а в секции финализации все восстанавливаем в первоначальном виде.

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

В качестве примера рассмотрим вариант реализации класса исключения для консольного приложения:

   type
     LConsoleException = class(LException)
     private
       FMsg: string;
     protected
       function GetExceptionMessage: string; override;
       procedure ShowException; override;
     public
       constructor Create(Msg: string);
     end;
   
   { EConsoleException }
   
   constructor LConsoleException.Create(Msg: string);
   begin
     FMsg:=Msg;
   end;
   
   function LConsoleException.GetExceptionMessage: string;
   begin
     Result:=ExceptionTitle + ': ' + FMsg + 
             ' at address ' + GetAddrString + #13#10;
   end;
   
   procedure LConsoleException.ShowException;
   var
     s: string;
     Len: DWORD;
     H: THandle;
   begin
     s:=ExceptionMessage;
     Len:=Length(s);
     H:=GetStdHandle(STD_ERROR_HANDLE);
     if H <> INVALID_HANDLE_VALUE then
     begin
       WriteConsole(H, PChar(s), Len, Len, nil);
       CloseHandle(H);
     end
     else
       inherited;
   end;

Мы переопределили конструктор, чтобы установить текст сообщения, и перекрыли два метода: GetExceptionMessage, чтобы отформатировать сообщение, и ShowException, чтобы перенаправить сообщение в стандартный вывод консоли. Генерация этого исключения вне защитного блока приведет к записи в стандартный вывод консоли сообщения об ошибке и завершению приложения. Если же его поместить в блок try-except, мы получим возможность вывести в консоль сообщение об ошибке и продолжить выполнение программы.

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

Набережных Сергей
25 августа 2003г.






Смотрите также материалы по темам:
[Exception] [Консольные приложения] [Исключения (exceptions)]

 Обсуждение материала [ 29-08-2003 12:07 ] 2 сообщения
  
Время на сайте: GMT минус 5 часов

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

Web hosting for this web site provided by DotNetPark (ASP.NET, SharePoint, MS SQL hosting)  
Software for IIS, Hyper-V, MS SQL. Tools for Windows server administrators. Server migration utilities  

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

Яндекс цитирования