Mirror

Displaying Custom Exceptions Dialogs and write Exceptions to the NT Event Log (component set) (Views: 703)


Problem/Question/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. 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.




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.




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 DKB 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.


Component Download: http://www.gatenetwork.com/delphi-samples/d3k/Except.zip

<< Back to main page