Visit our Sponsor   Visit our Sponsor
delphi3000.com - the free delphi knowledge platform
delphi3000.com - the free delphi knowledge platform
Have a look at your member-status

connecting people's knowledge


  - Recent ArticlesRSS feed for Recent Articles on delphi3000.com
  - List of All Articles
  - Top Viewed Articles
  - Articles (+Attachem.)
  - Articles Of Interest
  - Categories
  - Top Uploader
  - Search
  - Index

  - My Home
  - Submit an Article
  - My Articles
  - My Personal Data
  - My Bookmarks
  - Activities
  - Login/Logout

  - Sign Up
  - Why Sign Up
  - Newsletter

  - Press
  - Advertise

  - Contact
  - Feedback





Community
Borland
ClubeDelphi
Dr. Bob
UK-BUG
Delphi Meetings
Planeta Delphi







Startblatt.de






Share this article with friendsShare this article with friends
Rate this articleRate this article - to keep the quality of delphi3000.com !
Comment this article or read through previous comments (9)


Displaying Custom Exceptions Dialogs and write Exceptions to the NT Event Log (component set)Component available for this articleFormat this article printer-friendly!Bookmark function is only available for registered users!
Knowing what goes wrong; when, where (and why?).
Product:
Delphi 4.x (or higher)
Category:
Object Pascal
Skill Level:
Scoring:
Last Update:
04/25/2005
Search Keys:
delphi delphi3000 article borland vcl code-snippet exception handler component error messages Application.OnException
Times Scored:
19
Visits:
9181
Uploader: Daniel Wischnewski
Company: Delphi-PRAXiS
Reference: Delphi-PRAXiS
Component Download: http://www.gatenetwork.com/delphi-samples/d3k/Except.zip
 
Question/Problem/Abstract:
Often, the simple message box (and its ping) are just annoying, and they don't tell us where exactly our problem has started. This component will allow you to override Delphis standard exception handler and create your custom exception dialogs as you want them to look.
Answer:



Introduction

Delphi has become (one of) the best programming language/tool on the market. And by now, everyone should be aware of the fact, that you can control and manage every fact of your Delphi application. Just how can we? Well, this time, we are going to look into Delphi Exception handling and start to go new ways.

If you are interessted in a more detailed introduction to component writing and especially this component, come and read my German course on "Component Developement" in the German Delphi-PRAXiS community. (Happy Birthday Christian S.) It has just started, and this component will be part of the next few lectures.

The way Delphi goes

Usually, when you have an untrapped exception, you get a simple message box, that displays the error message - that's it.

standard error message


This error message does not give any useful information to most users and certainly it doesn't help programmers most the time either. What we need is just more.

Open up, Delphi!

Delphis Application object has an event property named Application.OnException. This is our entry point to start catching all unhandled exceptions. The event handler is defined as

  TExceptionEvent = procedure (Sender: TObject; E: Exception) of object;

At the end of this article, you will be able to display dialog boxes for all exceptions like this one - or any other way you want.

custom error message


Going further

Another step we want to take is the Windows NT Event Log. Our component can write error messages to the log. At design-time you'll simply have to add the component TEventLog, bundled together with this component download, to the project and assign it to the TExceptionManager component. (Unit: EventLog)

Further, we enhance this component by analyzing the mapping file, created during compilation by Delphi. A seperate class (not as component) will take care of analyzing the mapping file and, at run-time, anaylze the last error occured and retrieve information about unit name and method as well as the line number, where the error has occured. The mapping file has to have the same name as the EXE file of the application, with the extension .map. It has to be in the same directory. (Unit: MapFile)

Note: You have to turn on the creation of the mapping file in Delphi.
  Menu: Projet | Options
  Page: Linker
  Map File: Detailed

Note: Further information on mapping files you'll find at the D3K article Advanced Debug manager (Exception handler) by Olivier Rogier.

The frame work

During application start up we will create the actual exception handler (TExceptionHandler) in the background. (Unit: ExceptionHandler) Since only one Exception Manager (TExceptionManager) can work at any time, our exception handler will take care of the right assignments. Since the Exception Handler will not be created at design-time automatically, we have to take care of this separately.

When an Exception Manager wants to take control of exceptions occuring, we will set its Active property to true. In the background our Exception Manager will "tell" the Exception Handler that it takes control. When another Exception Manager takes control, the Exception Handler will acknowledge the fact and pass on the control.

The Exception Handler

Two more methods I want to explain shortly.

    procedure ExceptionHandler(Sender: TObject; ExceptObject: Exception);

The method ExceptionHandler will be assigned to the Application.OnException Event. All exceptions will be passed to this event handler.

...
    // analyze exception
    
FMapFile.LoadExceptionData;
    // handle exception
    
Handled := False;
    if Assigned(FCurrentManager.OnException) then
      
// event handler is assigned
      
FCurrentManager.OnException(
        Sender, ExceptObject, ExceptAddr, FMapFile.ExceptionAnalyzed,
        FMapFile.ExceptAddress, FMapFile.ExceptUnitName,
        FMapFile.ExceptMethodName, FMapFile.ExceptLineNumber, Handled
      );
    if Handled then
      
// the event handler has finished processing message, stop
      
Exit;
...

First we will try to analyze the mapping file. Next we check for a custom event handler with the current Exception Manager and pass on the event. If the custom handler has finished all work we'll stop, otherwise we continue with the default event handling.

    procedure DeactivateExceptionHandler; override;

The method DeactivateExceptionHandler will check, whether our Exception Handler is active. In this case it will assign the saved default excpetion handler back to the Application.OnException event (usually nil) and cancel the current manager.

...
if ThisHandlerIsActive then
  begin
    
// disable exception Manager
    
Application.OnException := FDelphiExceptionHandler;
    FDelphiExceptionHandler := nil;
    FCurrentManager := nil;
  end;
...

The Exception Manager

The Exception Manager provides different properties that allow the programmer to define the behavior during an exception.
  • Active - Set to True to activate the Exception Manager. Only one can be active at any time. The others will be set to inactive, automatically.
  • Eventlog - Assign an Eventlog component to this property if you want the Exception Handler to log exceptions into the Windows NT Eventlog. It will skip automatically on other Windows systems.
  • MessageDetails - Turn on/off the information you want to show to the user/save to the eventlog.
  • Options - Turn on/off the actions you want the Event Handler to take during an exception.


THE CODE SNIPPETS

You can either start with these or simply download the component and the sample application.

Download here

====================================================
The Eventlog
====================================================

{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Unit Name : EventLog
* Autor     : Daniel Wischnewski
* Copyright : Copyright © 2002 by gate(n)etwork. All Right Reserved.
* Urheber   : Daniel Wischnewski
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}

unit EventLog;

interface

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

type
  
TEventLog = class(TComponent)
  private
    
FConnected: Boolean;
    FTypesSupported: Integer;
    FCategoryCount: Integer;
    FCategoryMessageFile: String;
    FEventSource: String;
    FEventMessageFile: String;
    FEventLog: THandle;
    FMachine: String;
    function GetOSCanLogEvents: Boolean;
    procedure SetCategoryCount(const Value: Integer);
    procedure SetCategoryMessageFile(const Value: String);
    procedure SetEventMessageFile(const Value: String);
    procedure SetEventSource(const Value: String);
    procedure SetTypesSupported(const Value: Integer);
    procedure SetConnected(const Value: Boolean);
    procedure DoConnect(const Value: Boolean);
    procedure SetMachine(const Value: String);
    { Private declarations }
  
protected
    
{ Protected declarations }
  
public
    
{ Public declarations }
    
constructor Create(aOwner: TComponent); override;
    destructor Destroy; override;
    procedure RegisterEventSource;
    procedure LogEvent(
      Message: TStrings; Data: String = ''; aEventID: Word = 0;
      aCategoryID: Word = 1; aEventType: Word = EVENTLOG_ERROR_TYPE
    ); overload;
  published
    
{ Published declarations }
    
property OSCanLogEvents: Boolean read GetOSCanLogEvents;
    property EventSource: String read FEventSource write SetEventSource;
    property Machine: String read FMachine write SetMachine;
    property CategoryMessageFile: String read FCategoryMessageFile write SetCategoryMessageFile;
    property EventMessageFile: String read FEventMessageFile write SetEventMessageFile;
    property CategoryCount: Integer read FCategoryCount write SetCategoryCount;
    property TypesSupported: Integer read FTypesSupported write SetTypesSupported;
    property Connected: Boolean read FConnected write SetConnected;
  end;

procedure Register;

implementation

uses
  
Registry;

{$R *.DCR}

procedure Register;
begin
  
RegisterComponents('gate(n)etwork', [TEventLog]);
end;

function IsNT: Boolean;
var
OSVersion: TOSVersionInfo;
OSId: Integer;
begin
with OSVersion do
begin
dwOSVersionInfoSize := sizeOf(TOSVersionInfo);
if not getVersionEx(OSVersion) then
OSId := -1
else
OSId := dwPlatformId;
end;
Result := (OSId = VER_PLATFORM_WIN32_NT);
end;

{ TEventLog }

constructor TEventLog.Create(aOwner: TComponent);
begin
  inherited
Create(aOwner);
  FConnected := False;
  FTypesSupported := 1;
  FCategoryCount := 1;
  FCategoryMessageFile := '';
  FEventSource := Application.Name;
  FEventMessageFile := '';
  FEventLog := 0;
  FMachine := '';
end;

destructor TEventLog.Destroy;
begin
  
DoConnect(False);
  inherited Destroy;
end;

procedure TEventLog.DoConnect(const Value: Boolean);
begin
  if
csDesigning in ComponentState then
    
FConnected := Value and (FEventSource <> '')
  else if FEventSource <> '' then
    if
(FConnected <> Value) and OSCanLogEvents then
    begin
      if
FConnected then
      begin
        
DeregisterEventSource(FEventLog);
        FEventLog := 0;
      end else begin
        if
FMachine <> '' then
          
FEventLog := Windows.RegisterEventSource(
            PChar(FMachine), PChar(FEventSource)
          )
        else
          
FEventLog := Windows.RegisterEventSource(nil, PChar(FEventSource));
      end;
      FConnected := FEventLog <> 0;
    end;
end;

function TEventLog.GetOSCanLogEvents: Boolean;
begin
  
Result := IsNT;
end;

procedure TEventLog.LogEvent(
  Message: TStrings; Data: String; aEventID, aCategoryID, aEventType: Word
);
var
I: Integer;
MessageStr: array of PChar;
MessageCount: Word;
begin
  if
Connected then
  begin
MessageCount := Message.Count;
SetLength(MessageStr, MessageCount);
    try
      for
I := 0 to MessageCount - 1 do
        
MessageStr[I] := StrNew(PChar(Message.Strings[I]));
      try
        if
Data <> '' then
          
Windows.ReportEvent(
            FEventLog, aEventType, aCategoryID, aEventID, nil, MessageCount,
            Length(Data), MessageStr, @Data[1]
          )
        else
          
Windows.ReportEvent(
            FEventLog, aEventType, aCategoryID, aEventID, nil, MessageCount, 0,
            MessageStr, nil
          
)
      finally
        for
I := 0 to MessageCount - 1 do
          
StrDispose(MessageStr[I]);
      end;
    finally
      
SetLength(MessageStr, 0);
    end;
  end;
end;

procedure TEventLog.RegisterEventSource;
begin
  with
TRegistry.Create(
    STANDARD_RIGHTS_ALL or KEY_SET_VALUE or KEY_CREATE_SUB_KEY
  ) do
  try
    
RootKey := HKEY_LOCAL_MACHINE;
    if OpenKey(
      '\SYSTEM\CurrentControlSet\Services\Eventlog\Application\' + FEventSource,
      True
    ) then
    try
      
WriteString('CategoryMessageFile', FCategoryMessageFile);
      WriteString('EventMessageFile', FEventMessageFile);
      WriteInteger('CategoryCount', FCategoryCount);
      WriteInteger('TypesSupported', FTypesSupported);
    finally
      
CloseKey;
    end;
  finally
    
Free;
  end;
end;

procedure TEventLog.SetCategoryCount(const Value: Integer);
begin
  
FCategoryCount := Value;
end;

procedure TEventLog.SetCategoryMessageFile(const Value: String);
begin
  
FCategoryMessageFile := Value;
end;

procedure TEventLog.SetConnected(const Value: Boolean);
begin
  if
FEventSource = '' then
    
DoConnect(False)
  else
    
DoConnect(Value);
end;

procedure TEventLog.SetEventMessageFile(const Value: String);
begin
  
FEventMessageFile := Value;
end;

procedure TEventLog.SetEventSource(const Value: String);
begin
  
FEventSource := Value;
  if FEventSource = '' then
    
DoConnect(False)
  else if Connected then
  begin
    
DoConnect(False);
    DoConnect(True);
  end;
end;

procedure TEventLog.SetMachine(const Value: String);
begin
  if
FMachine <> Value then
  begin
    
FMachine := Value;
    if (FEventSource <> '') and Connected then
    begin
      
DoConnect(False);
      DoConnect(True);
    end;
  end;
end;

procedure TEventLog.SetTypesSupported(const Value: Integer);
begin
  
FTypesSupported := Value;
end;

end.

====================================================
The Exception Handler
====================================================

{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Unit Name : ExceptionHandler
* Autor     : Daniel Wischnewski
* Copyright : Copyright © 2002 by gate(n)etwork. All Right Reserved.
* Urheber   : Daniel Wischnewski
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}

unit ExceptionHandler;

interface

uses
  
Classes, SysUtils, Forms, Windows,
  ExceptionManager, MapFile;

type
  
TBaseExceptionHandler = class
  private
  protected
    function
GetActive(Manager: TExceptionManager): Boolean; virtual; abstract;
    procedure SetActive(
      Manager: TExceptionManager; Value: Boolean
    ); virtual; abstract;
  public
    procedure
RegisterManager(
      const Manager: TExceptionManager
    ); virtual; abstract;
    procedure UnRegisterManager(
      const Manager: TExceptionManager
    ); virtual; abstract;

    procedure DeactivateExceptionHandler; virtual; abstract;

    property Active[Manager: TExceptionManager]: Boolean
      read GetActive write SetActive;
  end;

function GetExceptionHandler: TBaseExceptionHandler;

implementation

var
  
gExceptionHandler: TBaseExceptionHandler;
  gDesignModus: Boolean = True;

type
  
TExceptionHandler = class(TBaseExceptionHandler)
  private
    
FDelphiExceptionHandler: TExceptionEvent;
    FCurrentManager: TExceptionManager;
    FExceptionManagers: TList;
    FMapFile: TMapFile;
    function ThisHandlerIsActive: Boolean;
  protected
    function
GetActive(Manager: TExceptionManager): Boolean; override;
    procedure SetActive(Manager: TExceptionManager; Value: Boolean); override;
    procedure ExceptionHandler(Sender: TObject; ExceptObject: Exception);
  public
    constructor
Create;
    destructor Destroy; override;
    procedure RegisterManager(const Manager: TExceptionManager); override;
    procedure UnRegisterManager(const Manager: TExceptionManager); override;
    procedure DeactivateExceptionHandler; override;
  end;

function GetExceptionHandler: TBaseExceptionHandler;
begin
  if
gExceptionHandler = nil then
    
gExceptionHandler := TExceptionHandler.Create;
  Result := gExceptionHandler;
end;

{ TExceptionHandler }

constructor TExceptionHandler.Create;
begin
  inherited
Create;
  FDelphiExceptionHandler := nil;
  FExceptionManagers := TList.Create;
  FMapFile := TMapFile.Create;
  FMapFile.MapFileName := ChangeFileExt(Application.ExeName, '.map');
end;

procedure TExceptionHandler.DeactivateExceptionHandler;
begin
  if
ThisHandlerIsActive then
  begin
    
// disable exception Manager
    
Application.OnException := FDelphiExceptionHandler;
    FDelphiExceptionHandler := nil;
    FCurrentManager := nil;
  end;
end;

destructor TExceptionHandler.Destroy;
begin
  
DeactivateExceptionHandler;
  FreeAndNil(FMapFile);
  FreeAndNil(FExceptionManagers);
  inherited Destroy;
end;

procedure TExceptionHandler.ExceptionHandler(
  Sender: TObject; ExceptObject: Exception
);
var
  
Handled: Boolean;
  SL: TStringList;
begin
  if
FCurrentManager <> nil then
  begin
    
// analyze exception
    
FMapFile.LoadExceptionData;
    // handle exception
    
Handled := False;
    if Assigned(FCurrentManager.OnException) then
      
// event handler is assigned
      
FCurrentManager.OnException(
        Sender, ExceptObject, ExceptAddr, FMapFile.ExceptionAnalyzed,
        FMapFile.ExceptAddress, FMapFile.ExceptUnitName,
        FMapFile.ExceptMethodName, FMapFile.ExceptLineNumber, Handled
      );
    if Handled then
      
// the event handler has finished processing message, stop
      
Exit;

    // create message
    
SL := TStringList.Create;
    try
      if
mdMessage in FCurrentManager.MessageDetails then
      begin
        
SL.Add(ExceptObject.Message);
        if FCurrentManager.MessageDetails - [mdMessage] <> [] then
          
SL.Add('');
      end;
      if FMapFile.ExceptionAnalyzed then
      begin
        if
mdAddress in FCurrentManager.MessageDetails then
        begin
          
SL.Add('Exception Address: ' + IntToHex(FMapFile.ExceptAddress, 8));
          if FCurrentManager.MessageDetails - [mdMessage, mdAddress] <> [] then
            
SL.Add('');
        end;
        if mdSourceInformation in FCurrentManager.MessageDetails then
        begin
          
SL.Add('Information about Source of Exception');
          SL.Add('Unit: ' + FMapFile.ExceptUnitName);
          SL.Add('Method: ' + FMapFile.ExceptMethodName);
          SL.Add('Line: ' + IntToStr(FMapFile.ExceptLineNumber));
        end;
      end;
      if eoShowMessageToUser in FCurrentManager.Options then
        
MessageBox(
          0, PChar(SL.Text), PChar('Exception handled: ' + FCurrentManager.Name),
          MB_OK or MB_ICONERROR
        );
      if eoLogToNTEventLog in FCurrentManager.Options then
        if
Assigned(FCurrentManager.EventLog) then
          
FCurrentManager.EventLog.LogEvent(SL);
    finally
      
SL.Free;
    end;
    if eoTerminateOnException in FCurrentManager.Options then
      
Application.Terminate;
  end;
end;

function TExceptionHandler.GetActive(Manager: TExceptionManager): Boolean;
begin
  
Result := ThisHandlerIsActive and (FCurrentManager = Manager);
end;

procedure TExceptionHandler.RegisterManager(const Manager: TExceptionManager);
begin
  if
FExceptionManagers.IndexOf(Manager) < 0 then
    
FExceptionManagers.Add(Manager);
end;

procedure TExceptionHandler.SetActive(
  Manager: TExceptionManager; Value: Boolean
);
begin
  if
Value <> Active[Manager] then
    if
Value and Assigned(Manager) then
    begin
      
// check for design mode
      
if not gDesignModus then
      begin
        
// enable exception Manager
        
if not ThisHandlerIsActive then
          
FDelphiExceptionHandler := Application.OnException;
        Application.OnException := ExceptionHandler;
        FCurrentManager := Manager;
      end;
    end else begin
      
DeactivateExceptionHandler;
    end;
end;

function TExceptionHandler.ThisHandlerIsActive: Boolean;
var
  
MyEH: TExceptionEvent;
begin
  
// get handle to lokal exception Manager
  
MyEH := ExceptionHandler;
  // compare to global exception Manager
  
Result := (Addr(Application.OnException) = Addr(MyEH));
end;

procedure TExceptionHandler.UnRegisterManager(const Manager: TExceptionManager);
begin
  
// remove manager from controlled list
  
if FExceptionManagers.IndexOf(Manager) >= 0 then
    
FExceptionManagers.Remove(Manager);
  Active[Manager] := False;
  
  if gDesignModus then
    
// during design-time
    
if FExceptionManagers.Count = 0 then
    begin
      
// destroy the exception Manager if last manager is removed from list
      
gExceptionHandler := nil;
      Destroy;
    end;
end;

initialization
  
// this part will not be executed at design-time
  
gExceptionHandler := TExceptionHandler.Create;
  // therefore we can fetch the design-time state
  
gDesignModus := False;
finalization
  
// free all stuff :-)
  
FreeAndNil(gExceptionHandler);
end.

====================================================
The Exception Manager
====================================================

{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Unit Name : ExceptionManager
* Autor     : Daniel Wischnewski
* Copyright : Copyright © 2002 by gate(n)etwork. All Right Reserved.
* Urheber   : Daniel Wischnewski
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}

unit ExceptionManager;

interface

uses
  
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  EventLog;

type
  
// different possibilities for handling exceptions
  
TExceptionOptions = (
    eoShowMessageToUser, eoLogToNTEventLog, eoTerminateOnException
  );
  TExceptionOptionSet = set of TExceptionOptions;

  // options for message details to display
  
TMessageDetails = (
    mdMessage, mdAddress, mdSourceInformation
  );
  TMessageDetailSet = set of TMessageDetails;

  // definition for custom exception handler
  
TCustomExceptionHandler = procedure(
    Sender: TObject; ExceptObject: Exception; ExceptionAddr: Pointer;
    ExceptionAnalyzed: Boolean; Address: DWORD; UnitName, MethodName: String;
    LineNum: DWORD; var Handled: Boolean
  ) of object;

  TExceptionManager = class(TComponent)
  private
    
FOptions: TExceptionOptionSet;
    FMessageDetails: TMessageDetailSet;
    FOnException: TCustomExceptionHandler;
    FEventLog: TEventLog;
    function GetActive: Boolean;
    procedure SetActive(const Value: Boolean);
    procedure SetOptions(const Value: TExceptionOptionSet);
    procedure SetMessageDetails(const Value: TMessageDetailSet);
    procedure SetOnException(const Value: TCustomExceptionHandler);
    { Private declarations }
  
protected
    
{ Protected declarations }
    
procedure Notification(
      aComponent: TComponent; Operation: TOperation
    ); override;
  public
    
{ Public declarations }
    
constructor Create(aOwner: TComponent); override;
    destructor Destroy; override;
    procedure DeactivateAllManagers;
  published
    
{ Published declarations }
    
property Active: Boolean read GetActive write SetActive;
    property Options: TExceptionOptionSet read FOptions write SetOptions;
    property MessageDetails: TMessageDetailSet
      read FMessageDetails write SetMessageDetails;
    property OnException: TCustomExceptionHandler
      read FOnException write SetOnException;
    property EventLog: TEventLog read FEventLog write FEventLog;
  end;

procedure Register;

implementation

uses
  
ExceptionHandler;

{$R *.DCR}

procedure Register;
begin
  
RegisterComponents('gate(n)etwork', [TExceptionManager]);
end;

{ TExceptionManager }

constructor TExceptionManager.Create(aOwner: TComponent);
begin
  inherited
Create(aOwner);
  GetExceptionHandler.RegisterManager(Self);
  FOptions := [eoShowMessageToUser];
  FMessageDetails := [mdMessage, mdAddress, mdSourceInformation];
  FOnException := nil;
  FEventLog := nil;
end;

procedure TExceptionManager.DeactivateAllManagers;
begin
  
GetExceptionHandler.DeactivateExceptionHandler;
end;

destructor TExceptionManager.Destroy;
begin
  
GetExceptionHandler.UnRegisterManager(Self);
  inherited Destroy;
end;

function TExceptionManager.GetActive: Boolean;
begin
  
Result := GetExceptionHandler.Active[Self];
end;

procedure TExceptionManager.Notification(aComponent: TComponent;
  Operation: TOperation);
begin
  inherited
Notification(aComponent, Operation);
  if (Operation = opRemove) then
  begin
    if
aComponent = FEventLog then
      
FEventLog := nil;
  end;
end;

procedure TExceptionManager.SetActive(const Value: Boolean);
begin
  
GetExceptionHandler.Active[Self] := Value;
end;

procedure TExceptionManager.SetMessageDetails(const Value: TMessageDetailSet);
begin
  
FMessageDetails := Value;
end;

procedure TExceptionManager.SetOnException(
  const Value: TCustomExceptionHandler
);
begin
  
FOnException := Value;
end;

procedure TExceptionManager.SetOptions(const Value: TExceptionOptionSet);
begin
  
FOptions := Value;
end;

end.

====================================================
The Mapping File
====================================================

{* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Unit Name : MapFile
* Autor     : Daniel Wischnewski
* Copyright : Copyright © 2002 by gate(n)etwork. All Right Reserved.
* Urheber   : Daniel Wischnewski
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}

unit MapFile;

interface

uses
  
SysUtils, Classes, Windows;

type
  
TMapFile = class
  private
    
FMapFileName: String;
    FSegmentData, FAdressData, FLineData: TStringList;
    FMapFileBase: DWORD;
    FExceptAddress: DWORD;
    FExceptLineNumber: Integer;
    FExceptMethodName: String;
    FExceptUnitName: String;
    FExceptionAnalyzed: Boolean;
    procedure SetMapFileName(const Value: String);
    procedure LoadMapFile;
  protected
  public
    constructor
Create;
    destructor Destroy; override;

    procedure LoadExceptionData(Address: Pointer = nil);

    property MapFileName: String read FMapFileName write SetMapFileName;
    property MapFileBase: DWORD read FMapFileBase write FMapFileBase;
    property ExceptUnitName: String read FExceptUnitName;
    property ExceptMethodName: String read FExceptMethodName;
    property ExceptLineNumber: Integer read FExceptLineNumber;
    property ExceptAddress: DWORD read FExceptAddress;
    property ExceptionAnalyzed: Boolean read FExceptionAnalyzed;
  end;

implementation

{ TMapFile }

constructor TMapFile.Create;
begin
  inherited
Create;
  FSegmentData := TStringList.Create;
  FAdressData := TStringList.Create;
  FLineData := TStringList.Create;
  FMapFileName := '';
  FMapFileBase := $00401000;
  FExceptAddress := 0;
  FExceptLineNumber := 0;
  FExceptMethodName := '';
  FExceptUnitName := '';
  FExceptionAnalyzed := False;
end;

destructor TMapFile.Destroy;
begin
  
FreeAndNil(FSegmentData);
  FreeAndNil(FAdressData);
  FreeAndNil(FLineData);
  inherited Destroy;
end;

procedure TMapFile.LoadExceptionData(Address: Pointer);
var
  
UnitLineDataFound: Boolean;
  I, J, LastLine: Integer;
  Start, Stop, ProcAddr, LineAddr: DWORD;
  Line: String;
begin
  
// reset
  
FExceptAddress := 0;
  FExceptLineNumber := 0;
  FExceptMethodName := '';
  FExceptUnitName := '';
  FExceptionAnalyzed := False;

  // load address
  
if Address = nil then
    
Address := ExceptAddr;
  if Address = nil then
    
Exit;

  // load and adjust exception address
  
FExceptAddress := DWORD(Address) - FMapFileBase;

  // find unit of exception
  
I := 0;
  while I < FSegmentData.Count do
  begin
    try
      
// check whether address is within unit address limits
      
Start := DWORD(StrToInt('0x' + Copy(FSegmentData[I], 7, 8)));
      Stop := Start + DWORD(StrToInt('0x' + Copy(FSegmentData[I], 16, 8)));
      if (Start <= FExceptAddress) and (FExceptAddress < Stop) then
      begin
        
Start := Pos('M=', FSegmentData[I]) + 2;
        Stop := Pos('ACBP=', FSegmentData[I]);
        if (Start > 0) and (Stop > 0) then
          
FExceptUnitName :=
            Trim(Copy(FSegmentData[I], Start, Stop - Start - 1));
      end;
    except
    end
;
    Inc(I);
  end;

  // find function of exception
  
I := 0;
  while I < FAdressData.Count do
  begin
    try
      
ProcAddr := DWORD(StrToInt('0x' + Copy(FAdressData[I], 7, 8)));
      if ProcAddr >= FExceptAddress then
      begin
        if
ProcAddr = FExceptAddress then
          
Line := FAdressData[I]
        else
          
Line := FAdressData[Pred(I)];
        FExceptMethodName := Trim(Copy(Line, 22, Length(Line)));
        Break;
      end;
    except
    end
;
    Inc(I);
  end;

  // find line number of exception
  
I := 0;
  UnitLineDataFound := False;
  // search for unit section
  
while I < FLineData.Count do
  begin
    if
Pos(FExceptUnitName, FLineData[I]) <> 0 then
    begin
      
UnitLineDataFound := True;
      Break;
    end;
    Inc(I);
  end;
  if UnitLineDataFound then
  begin
    
// search for line number
    
LastLine := 0;
    LineAddr := 0;
    Inc(I, 2);
    while I < FLineData.Count do
    begin
      if
Pos('Line numbers for', FLineData[I]) <> 0 then
        
Break;
      try
        for
J := 0 to 3 do
        begin
          
LineAddr := StrToInt('0x' + Copy(FLineData[I], J * 20 + 13, 8));
          if LineAddr > FExceptAddress then
            
Break;
          LastLine := StrToInt(Trim(Copy(FLineData[I], J * 20 + 1, 6)));
          if LineAddr = FExceptAddress then
            
Break;
        end;
      except
      end
;
      Inc(I);
    end;
    if LineAddr >= FExceptAddress then
      
FExceptLineNumber := LastLine;
  end;

  FExceptionAnalyzed := True;
end;

procedure TMapFile.LoadMapFile;
var
  
I: Integer;
begin
  
FSegmentData.Clear;
  FAdressData.Clear;
  FLineData.Clear;
  if FileExists(FMapFileName) then
    with
TStringList.Create do
    try
      
LoadFromFile(FMapFileName);
      // find start of detailed segment block
      
I := 0;
      while I < Count do
        if
Pos('Detailed map of segments', Strings[I]) <> 0 then
          
Break
        else
          
Inc(I);
      Inc(I, 2);

      // copy all lines to segment data, until name-address block starts
      
while I < Count do
        if
Pos('Address         Publics by Name', Strings[I]) <> 0 then
          
Break
        else begin
          
FSegmentData.Add(Strings[I]);
          Inc(I);
        end;

      // find start of value-address block
      
while I < Count do
        if
Pos('Address         Publics by Value', Strings[I]) <> 0 then
          
Break
        else
          
Inc(I);
      Inc(I, 3);

      // copy all lines to address data, until line number block starts
      
while I < Count do
        if
Pos('Line numbers for', Strings[I]) <> 0 then
          
Break
        else begin
          
FAdressData.Add(Strings[I]);
          Inc(I);
        end;

      // copy all remaining lines to line data
      
while I < Count do
      begin
        
FLineData.Add(Strings[I]);
        Inc(I);
      end;
    finally
      
Free;
    end;
end;

procedure TMapFile.SetMapFileName(const Value: String);
begin
  if
FMapFileName <> Value then
  begin
    
FMapFileName := Value;
    LoadMapFile;
  end;
end;

end.



Have fun,
Daniel Wischnewski





Please rate this article!
Skill level:
BeginnerExpert

Useful:
No!Very!

Overall rating:
PoorExcellent



Comments to this article
Write a new comment
Problems to use
    rodrigo ribeiro (May 3 2005 1:44PM)

   Hi!!

    Finding a component, to show custumize exception I found your component. I like so much that but I can't generate the MapFile. How can I do this?
  Tanks !!!
Respond

RE: Problems to use
Daniel Wischnewski (May 3 2005 1:47PM)

As it is said in the text above:

Note: You have to turn on the creation of the mapping file in Delphi.
  Menu: Project | Options
  Tab: Linker
  Option: Map File -> Detailed

Respond

Thanks!
    Little Nemo (Nov 18 2004 6:41PM)

Daniel, you are Great!!!!
Respond

RE: Thanks!
Daniel Wischnewski (Nov 19 2004 10:28AM)

Well thank you :-)

AFAIK in the Jedi Library there is now a much more complete solution, however, you have to install JCL and JVCL, so there is a possible drawback ;-)

Regards,
Daniel
Respond

Message Details - Source Information
    Pavithra Chandrasekaran (Jul 10 2002 12:30PM)

Hi,
   Your component was very useful. I tried using it in a DLL. I used the Project - Options-Linker tab and changed the mapping details as Detailed, but was not able to extract the Source details(Unit Name , Method Name, and Line number). Can you help me with this ? Thank you.
Respond

One Word: \/\/o\/\/
    Jonathon Hibbard (Jun 28 2002 4:11PM)

\/\/o\/\/ indeed. nice code. helps out a lot, always been looknig for a nice easy error handler. thanks for the tip!
Respond

RE: One Word: \/\/o\/\/
Daniel Wischnewski (Jun 28 2002 4:27PM)

Thank you - always great to read such comments. Have fun with it - I d, too.
Respond

RE: RE: One Word: \/\/o\/\/
soma (Jul 2 2002 1:06AM)

hi
as i see, your component only works when the exception was raised by the application, so its not as good as i thought.

i wan't to start your component when a real error occours
x:=0;
x:=x div 15;

and not only when i raise one.
is there a way?
Respond

RE: RE: RE: One Word: \/\/o\/\/
Daniel Wischnewski (Jul 2 2002 9:10AM)

Well, first I got to ask, where is the error in your sample?

X := 0 div 15;  --> X := 0; mathematically correct!

So, there may be a logical error, however, that is nothing where a program will stop working. This component is created to intercept fatal errors, where your application will stop to work the current procedure(s). That's what an exception is.
Respond














 
Sign up to consume product discounts for Bronze memberships !

read more


  Visit our Sponsor

 

  Community Ad of
E. Irigoyen
 
   














 







     
  Copyright © 2000 - 2007 delphi3000.com - All rights reserved. Terms of use. || Privacy
delphi3000.com is a service by bluestep.com IT-Services GmbH (Vienna)