Автоматическая обработка ошибок
Автор: Павел
Введение
Эта статья, в первую очередь, будет интересна начинающим программистам. Я расскажу об одном из методов обработки исключительных ситуаций, не очень распространенном, но довольно эффективном.
У всех
Типичная обработка ошибки заключается в том, чтобы обрамить блоки кода в конструкции try..except или try..finally. В общем, все по учебнику. Тем не менее, многие попросту не делают этого, так как им недосуг или обработка исключений оставляется "на потом". Когда подходит время сдачи проекта, нередко такие программисты начинают спешно латать дыры, что может дать не только положительный эффект, я имею ввиду появившуюся у программы обработку исключений, но и отрицательный. Дело в том, что обработка исключительных ситуаций тоже является частью кода, а внесение нового кода может повлечь за собой и внесение новых ошибок.
Избавиться от сложившейся ситуации не так трудно, как может показаться на первый взгляд. Почти все становится ясно, когда вспоминаешь про объект Application и его свойство OnException.
Не у всех
Как вы уже, наверное, догадались, свойство Application.OnException является глобальным обработчиком событий приложения. Перед тем как описать метод использования этого свойства, давайте договоримся, что у нас есть объект класса TgsCatcher (именно его я и описываю в данной статье), у которого есть метод TgsCatcer.Catcher, который и будет обработчиком ошибок приложения. Простейший вариант вышесказанного выглядит следующим образом:
unit gsCatcher;
//**! ----------------------------------------------------------
//**! This unit is a part of GSPackage project (Gregory Sitnin's
//**! Delphi Components Package).
//**! ----------------------------------------------------------
//**! You may use or redistribute this unit for your purposes
//**! while unit's code and this copyright notice is unchanged
//**! and exists.
//**! ----------------------------------------------------------
//**! (c) Gregory Sitnin, 2001-2002. All rights reserved.
//**! ----------------------------------------------------------
{***} interface {***}
uses Classes, SysUtils, JPEG;
type
TgsCatcher = class(TComponent)
private
FEnabled: boolean;
FGenerateScreenshot: boolean;
FJPEGScreenshot: boolean;
FJpegQuality: TJPEGQualityRange;
FCollectInfo: boolean;
Fn: TFilename;
procedure SetEnabled(const Value: boolean);
{ Private declarations }
protected
{ Protected declarations }
procedure EnableCatcher;
procedure DisableCatcher;
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Catcher(Sender: TObject; E: Exception);
published
{ Published declarations }
property Enabled: boolean read FEnabled write SetEnabled
default False;
end;
procedure Register;
{***} implementation {***}
uses Windows, Forms, Dialogs, Graphics;
procedure Register;
begin
RegisterComponents('Gregory Sitnin', [TgsCatcher]);
end;
{ TgsCatcher }
constructor TgsCatcher.Create(AOwner: TComponent);
begin
inherited;
end;
destructor TgsCatcher.Destroy;
begin
DisableCatcher;
inherited;
end;
procedure TgsCatcher.SetEnabled(const Value: boolean);
begin
FEnabled := Value;
if Enabled then
EnableCatcher
else
DisableCatcher;
end;
procedure TgsCatcher.DisableCatcher;
begin
Application.OnException := nil;
end;
procedure TgsCatcher.EnableCatcher;
begin
Application.OnException := Catcher;
end;
procedure TgsCatcher.Catcher(Sender: TObject; E: Exception);
begin
{TODO: Write some exception handling code}
end;
end.
Это компонент, который умеет подключаться и отключаться от обработчика Application.OnException при помощи установки свойства Enabled. В подключенном состоянии все возникшие в приложении исключительные ситуации перенаправляются методу TgsCatcher.Catcher, который с этими исключениями, пока, ничего не делает.
Теперь давайте пофантазируем, что бы мы хотели видеть в глобальном автоматическом обработчике ошибок. Мне, к примеру, очень пригодился бы скриншот (снимок экрана) активного окна, который автоматически бы был записан в файл под уникальным именем. Также мне бы хотелось иметь описание текущего окружения системы и описание проблемы.
Реализация функций
Для начала, давайте снимем скриншот активного окна. Сделать это довольно легко. В секцию implementation подключаем модуль Graphics.
Лирическое отступление: Считается неплохим стилем, подключать модули в секцию implementation, если их информация требуется только в этой секции. Например, в секции interface я нигде не использовал ни одного класса или типа, который описан в модуле Graphics, поэтому и занес его в implementation.
После подключения вышеназванного модуля мы получили возможность работы с изображениями в формате BMP, то есть, мы можем описать объект класса TBitmap и получить в него снимок активного окна при помощи следующего вызова:
bmp := Screen.ActiveForm.GetFormImage;
Сам объект класса TBitmap создавать нет необходимости, так как он будет автоматически создан вызываемым методом GetFormImage. Теперь нам хорошо бы было полученное изображение сохранить в файл, а для этого не плохо бы было назвать файл уникальным именем. Надо заметить, что в Windows, как и во многих операционных системах есть специальный механизм создания действительно уникальных имен файлов. Однако, имена файлов на выходе этих механизмов являются, по большей части, ничего не значащими наборами символов. Но суть этой технологии в уникальности, а это она делает. Мне же хотелось, чтобы имена несли смысловую нагрузку. Было бы здорово, если бы в них было записано имя исполняемого файла, имя формы, дата и время возникновения ошибки. Решается данная задача вот таким образом (Fn - переменная строкового типа):
Fn := ExtractFilename(Application.ExeName)+'_'+
Screen.ActiveForm.Name+
FormatDateTime('_ddmmyyyy_hhnnss',now)+
'_debug';
Таким образом, мы получили генератор имен файлов, создающий имена, очень похожие на "PROJECT1.EXE_Form1_22092002_171956_debug.bmp", только без расширения. Теперь, запишем изображение в файл.
bmp.SaveToFile(fn+'.bmp');
Итог - любое исключение в приложении вызовет автоматическую запись скриншота в файл формата BMP. Но, такой графический формат имеет одну неприятную особенность, дельфи умеет работать только с несжатыми файлами, которые благодаря этому имеют большой объем, а мне бы хотелось посылать эти файлы автоматически по e-mail, чтобы всегда быть в курсе ошибок программы.
К счастью, фирма Borland бесплатно приложила к Delphi библиотеку работы с изображениями в формате JPEG. Поищите на компакт-диске с дельфи или на своем жестком диске в каталогах дельфи файлы, начинающиеся с букв "jpeg".
Таким образом, подключаем модуль JPEG после модуля Graphics и пишем вот такой код:
procedure TgsCatcher.DoGenerateScreenshot;
var
bmp: TBitmap;
jpg: TJPEGImage;
begin
bmp := Screen.ActiveForm.GetFormImage;
begin
jpg := TJPEGImage.Create;
jpg.CompressionQuality := 100;
jpg.Assign(bmp);
jpg.SaveToFile(fn + '.jpg');
FreeAndNil(jpg);
end;
FreeAndNil(bmp);
end;
Теперь из метода Catcher достаточно просто вызвать метод DoGenerateScreenshot и автоматическое сохранение скриншота в формате JPEG с качеством 100% вам обеспечено.
Теперь, я хотел бы, чтобы генерировался текстовый отчет об ошибке. Это еще проще. Давайте, к примеру, сделаем отчет, в котором будут указаны имя компьютера и имя текущего пользователя. Пишем вот такие функции и процедуры:
function TgsCatcher.CollectUserName: string;
var
uname: pchar;
unsiz: cardinal;
begin
uname := StrAlloc(255);
unsiz := 254;
GetUserName(uname, unsiz);
if (unsiz > 0) then
Result := string(uname)
else
Result := 'n/a';
StrDispose(uname);
end;
Эта функция запросит у системы имя пользователя при помощи функции API GetUserName, и вернет строку "n/a", если имя пользователя получить не удалось.
function TgsCatcher.CollectComputerName: string;
var
cname: pchar;
cnsiz: cardinal;
begin
cname := StrAlloc(MAX_COMPUTERNAME_LENGTH + 1);
cnsiz := MAX_COMPUTERNAME_LENGTH + 1;
GetComputerName(cname, cnsiz);
if (cnsiz > 0) then
Result := string(cname)
else
Result := 'n/a';
StrDispose(cname);
end;
Эта функция аналогична функции CollectUserName, только запрашивает имя компьютера.
procedure TgsCatcher.DoCollectInfo(E: Exception);
var
sl: TStringList;
begin
sl := tstringlist.Create;
sl.add('--- This report is created by automated ' +
'reporting system.');
sl.add('Computer name is: [' + ComputerName + ']');
sl.add('User name is: [' + UserName + ']');
sl.add('--- End of report ----------------------' +
'-----------------');
sl.SaveToFile(Fn + '.txt');
end;
Последняя процедура создает объект класса TStringList, наполняет его информацией, которую надо записать и сохраняет в файл, с именем, аналогичном имени файла со скриншотом. Остается только прописать вызов этого метода в процедуре Catcher.
Заключение
Вот, собственно и все. Что же мы получили? Мы получили невизуальный компонент, который автоматически подключается к обработчику исключительных ситуаций приложения. При этом, обработчик умеет создавать скриншот активного окна и записывать в текстовый файл интересующие нас параметры. Вроде бы неплохо.
Конечно, нет предела совершенству. Компонент можно бы
было существенно улучшить, но это уже тема для отдельного разговора,
который я предлагаю продолжить у меня на специально созданном для
этого созданном форуме (кстати,
там же вы найдете и полный текст
модуля).
|