743 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			ObjectPascal
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			743 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			ObjectPascal
		
	
	
		
			Executable File
		
	
	
	
	
{**************************************************************************************************}
 | 
						|
{                                                                                                  }
 | 
						|
{ Project JEDI Code Library (JCL)                                                                  }
 | 
						|
{                                                                                                  }
 | 
						|
{ The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); }
 | 
						|
{ you may not use this file except in compliance with the License. You may obtain a copy of the    }
 | 
						|
{ License at http://www.mozilla.org/MPL/                                                           }
 | 
						|
{                                                                                                  }
 | 
						|
{ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF   }
 | 
						|
{ ANY KIND, either express or implied. See the License for the specific language governing rights  }
 | 
						|
{ and limitations under the License.                                                               }
 | 
						|
{                                                                                                  }
 | 
						|
{ The Original Code is ExceptDlg.pas.                                                              }
 | 
						|
{                                                                                                  }
 | 
						|
{ The Initial Developer of the Original Code is documented in the accompanying                     }
 | 
						|
{ help file JCL.chm. Portions created by these individuals are Copyright (C) of these individuals. }
 | 
						|
{                                                                                                  }
 | 
						|
{**************************************************************************************************}
 | 
						|
{                                                                                                  }
 | 
						|
{ Sample Application exception dialog replacement                                                  }
 | 
						|
{                                                                                                  }
 | 
						|
{ Last modified: April 1, 2003                                                                     }
 | 
						|
{                                                                                                  }
 | 
						|
{**************************************************************************************************}
 | 
						|
 | 
						|
unit UnitfrmExceptionHandler;
 | 
						|
 | 
						|
{$I jcl.inc}
 | 
						|
 | 
						|
interface
 | 
						|
 | 
						|
uses
 | 
						|
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
 | 
						|
  StdCtrls, ExtCtrls, JclDebug;
 | 
						|
 | 
						|
const
 | 
						|
  UM_CREATEDETAILS = WM_USER + $100;
 | 
						|
 | 
						|
  ReportToLogEnabled   = $00000001; // TExceptionDialog.Tag property
 | 
						|
  DisableTextScrollbar = $00000002; // TExceptionDialog.Tag property
 | 
						|
 | 
						|
type
 | 
						|
  TSimpleExceptionLog = class (TObject)
 | 
						|
  private
 | 
						|
    FLogFileHandle: THandle;
 | 
						|
    FLogFileName: string;
 | 
						|
    FLogWasEmpty: Boolean;
 | 
						|
    function GetLogOpen: Boolean;
 | 
						|
  protected
 | 
						|
    function CreateDefaultFileName: string;
 | 
						|
  public
 | 
						|
    constructor Create(const ALogFileName: string = '');
 | 
						|
    destructor Destroy; override;
 | 
						|
    procedure CloseLog;
 | 
						|
    procedure OpenLog;
 | 
						|
    procedure Write(const Text: string; Indent: Integer = 0); overload;
 | 
						|
    procedure Write(Strings: TStrings; Indent: Integer = 0); overload;
 | 
						|
    procedure WriteStamp(SeparatorLen: Integer = 0);
 | 
						|
    property LogFileName: string read FLogFileName;
 | 
						|
    property LogOpen: Boolean read GetLogOpen;
 | 
						|
  end;
 | 
						|
 | 
						|
  TExcDialogSystemInfo = (siStackList, siOsInfo, siModuleList, siActiveControls);
 | 
						|
  TExcDialogSystemInfos = set of TExcDialogSystemInfo;
 | 
						|
 | 
						|
  TExceptionDialog = class(TForm)
 | 
						|
    OkBtn: TButton;
 | 
						|
    DetailsMemo: TMemo;
 | 
						|
    DetailsBtn: TButton;
 | 
						|
    Bevel1: TBevel;
 | 
						|
    TextLabel: TMemo;
 | 
						|
    procedure FormPaint(Sender: TObject);
 | 
						|
    procedure FormCreate(Sender: TObject);
 | 
						|
    procedure FormShow(Sender: TObject);
 | 
						|
    procedure DetailsBtnClick(Sender: TObject);
 | 
						|
    procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
 | 
						|
    procedure FormDestroy(Sender: TObject);
 | 
						|
    procedure FormResize(Sender: TObject);
 | 
						|
  private
 | 
						|
    FDetailsVisible: Boolean;
 | 
						|
    FIsMainThead: Boolean;
 | 
						|
    FLastActiveControl: TWinControl;
 | 
						|
    FNonDetailsHeight: Integer;
 | 
						|
    FFullHeight: Integer;
 | 
						|
    FSimpleLog: TSimpleExceptionLog;
 | 
						|
    procedure CreateDetails;
 | 
						|
    function GetReportAsText: string;
 | 
						|
    procedure ReportToLog;
 | 
						|
    procedure SetDetailsVisible(const Value: Boolean);
 | 
						|
    procedure UMCreateDetails(var Message: TMessage); message UM_CREATEDETAILS;
 | 
						|
  protected
 | 
						|
    procedure AfterCreateDetails; dynamic;
 | 
						|
    procedure BeforeCreateDetails; dynamic;
 | 
						|
    procedure CreateDetailInfo; dynamic;
 | 
						|
    procedure CreateReport(const SystemInfo: TExcDialogSystemInfos);
 | 
						|
    function ReportMaxColumns: Integer; virtual;
 | 
						|
    function ReportNewBlockDelimiterChar: Char; virtual;
 | 
						|
    procedure NextDetailBlock;
 | 
						|
    procedure UpdateTextLabelScrollbars;
 | 
						|
  public
 | 
						|
    procedure CopyReportToClipboard;
 | 
						|
    class procedure ExceptionHandler(Sender: TObject; E: Exception);
 | 
						|
    class procedure ExceptionThreadHandler(Thread: TJclDebugThread);
 | 
						|
    class procedure ShowException(E: Exception; Thread: TJclDebugThread);
 | 
						|
    property DetailsVisible: Boolean read FDetailsVisible write SetDetailsVisible;
 | 
						|
    property ReportAsText: string read GetReportAsText;
 | 
						|
    property SimpleLog: TSimpleExceptionLog read FSimpleLog;
 | 
						|
  end;
 | 
						|
 | 
						|
  TExceptionDialogClass = class of TExceptionDialog;
 | 
						|
 | 
						|
var
 | 
						|
  ExceptionDialogClass: TExceptionDialogClass = TExceptionDialog;
 | 
						|
 | 
						|
implementation
 | 
						|
 | 
						|
{$R *.DFM}
 | 
						|
 | 
						|
uses
 | 
						|
  ClipBrd, Math,
 | 
						|
  JclBase, JclFileUtils, JclHookExcept, JclPeImage, JclStrings, JclSysInfo, JclSysUtils;
 | 
						|
 | 
						|
resourcestring
 | 
						|
  RsAppError = '%s - application error';
 | 
						|
  RsExceptionClass = 'Exception class: %s';
 | 
						|
  RsExceptionAddr = 'Exception address: %p';
 | 
						|
  RsStackList = 'Stack list, generated %s';
 | 
						|
  RsModulesList = 'List of loaded modules:';
 | 
						|
  RsOSVersion = 'System   : %s %s, Version: %d.%d, Build: %x, "%s"';
 | 
						|
  RsProcessor = 'Processor: %s, %s, %d MHz %s%s';
 | 
						|
  RsScreenRes = 'Display  : %dx%d pixels, %d bpp';
 | 
						|
  RsActiveControl = 'Active Controls hierarchy:';
 | 
						|
  RsThread = 'Thread: %s';
 | 
						|
  RsMissingVersionInfo = '(no version info)';
 | 
						|
 | 
						|
var
 | 
						|
  ExceptionDialog: TExceptionDialog;
 | 
						|
 | 
						|
//==================================================================================================
 | 
						|
// Helper routines
 | 
						|
//==================================================================================================
 | 
						|
 | 
						|
function GetBPP: Integer;
 | 
						|
var
 | 
						|
  DC: HDC;
 | 
						|
begin
 | 
						|
  DC := GetDC(0);
 | 
						|
  Result := GetDeviceCaps(DC, BITSPIXEL) * GetDeviceCaps(DC, PLANES);
 | 
						|
  ReleaseDC(0, DC);
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
function SortModulesListByAddressCompare(List: TStringList; Index1, Index2: Integer): Integer;
 | 
						|
begin
 | 
						|
  Result := Integer(List.Objects[Index1]) - Integer(List.Objects[Index2]);
 | 
						|
end;
 | 
						|
 | 
						|
//==================================================================================================
 | 
						|
// TApplication.HandleException method code hooking for exceptions from DLLs
 | 
						|
//==================================================================================================
 | 
						|
 | 
						|
// We need to catch the last line of TApplication.HandleException method:
 | 
						|
// [...]
 | 
						|
//   end else
 | 
						|
//    SysUtils.ShowException(ExceptObject, ExceptAddr);
 | 
						|
// end;
 | 
						|
 | 
						|
procedure HookShowException(ExceptObject: TObject; ExceptAddr: Pointer);
 | 
						|
begin
 | 
						|
  if JclValidateModuleAddress(ExceptAddr) and (ExceptObject.InstanceSize >= Exception.InstanceSize) then
 | 
						|
    TExceptionDialog.ExceptionHandler(nil, Exception(ExceptObject))
 | 
						|
  else
 | 
						|
    SysUtils.ShowException(ExceptObject, ExceptAddr);
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
function HookTApplicationHandleException: Boolean;
 | 
						|
const
 | 
						|
  CallOffset      = $86;
 | 
						|
  CallOffsetDebug = $94;
 | 
						|
type
 | 
						|
  PCALLInstruction = ^TCALLInstruction;
 | 
						|
  TCALLInstruction = packed record
 | 
						|
    Call: Byte;
 | 
						|
    Address: Integer;
 | 
						|
  end;
 | 
						|
var
 | 
						|
  TApplicationHandleExceptionAddr, SysUtilsShowExceptionAddr: Pointer;
 | 
						|
  CALLInstruction: TCALLInstruction;
 | 
						|
  CallAddress: Pointer;
 | 
						|
  NW: DWORD;
 | 
						|
 | 
						|
  function CheckAddressForOffset(Offset: Cardinal): Boolean;
 | 
						|
  begin
 | 
						|
    try
 | 
						|
      CallAddress := Pointer(Cardinal(TApplicationHandleExceptionAddr) + Offset);
 | 
						|
      CALLInstruction.Call := $E8;
 | 
						|
      Result := PCALLInstruction(CallAddress)^.Call = CALLInstruction.Call;
 | 
						|
      if Result then
 | 
						|
      begin
 | 
						|
        if IsCompiledWithPackages then
 | 
						|
          Result := PeMapImgResolvePackageThunk(Pointer(Integer(CallAddress) + Integer(PCALLInstruction(CallAddress)^.Address) + SizeOf(CALLInstruction))) = SysUtilsShowExceptionAddr
 | 
						|
        else
 | 
						|
          Result := PCALLInstruction(CallAddress)^.Address = Integer(SysUtilsShowExceptionAddr) - Integer(CallAddress) - SizeOf(CALLInstruction);
 | 
						|
      end;
 | 
						|
    except
 | 
						|
      Result := False;
 | 
						|
    end;    
 | 
						|
  end;
 | 
						|
 | 
						|
begin
 | 
						|
  TApplicationHandleExceptionAddr := PeMapImgResolvePackageThunk(@TApplication.HandleException);
 | 
						|
  SysUtilsShowExceptionAddr := PeMapImgResolvePackageThunk(@SysUtils.ShowException);
 | 
						|
  Result := CheckAddressForOffset(CallOffset) or CheckAddressForOffset(CallOffsetDebug);
 | 
						|
  if Result then
 | 
						|
  begin
 | 
						|
    CALLInstruction.Address := Integer(@HookShowException) - Integer(CallAddress) - SizeOf(CALLInstruction);
 | 
						|
    Result := WriteProcessMemory(GetCurrentProcess, CallAddress, @CALLInstruction, SizeOf(CALLInstruction), NW);
 | 
						|
    if Result then
 | 
						|
      FlushInstructionCache(GetCurrentProcess, CallAddress, SizeOf(CALLInstruction));
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
//==================================================================================================
 | 
						|
// TSimpleExceptionLog
 | 
						|
//==================================================================================================
 | 
						|
 | 
						|
procedure TSimpleExceptionLog.CloseLog;
 | 
						|
begin
 | 
						|
  if LogOpen then
 | 
						|
  begin
 | 
						|
    CloseHandle(FLogFileHandle);
 | 
						|
    FLogFileHandle := INVALID_HANDLE_VALUE;
 | 
						|
    FLogWasEmpty := False;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
constructor TSimpleExceptionLog.Create(const ALogFileName: string);
 | 
						|
begin
 | 
						|
  if ALogFileName = '' then
 | 
						|
    FLogFileName := CreateDefaultFileName
 | 
						|
  else
 | 
						|
    FLogFileName := ALogFileName;
 | 
						|
  FLogFileHandle := INVALID_HANDLE_VALUE;
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
function TSimpleExceptionLog.CreateDefaultFileName: string;
 | 
						|
begin
 | 
						|
  Result := PathExtractFileDirFixed(ParamStr(0)) + PathExtractFileNameNoExt(ParamStr(0)) + '_Err.log';
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
destructor TSimpleExceptionLog.Destroy;
 | 
						|
begin
 | 
						|
  CloseLog;
 | 
						|
  inherited;
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
function TSimpleExceptionLog.GetLogOpen: Boolean;
 | 
						|
begin
 | 
						|
  Result := FLogFileHandle <> INVALID_HANDLE_VALUE;
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
procedure TSimpleExceptionLog.OpenLog;
 | 
						|
begin
 | 
						|
  if not LogOpen then
 | 
						|
  begin
 | 
						|
    FLogFileHandle := CreateFile(PChar(FLogFileName), GENERIC_WRITE, FILE_SHARE_READ, nil,
 | 
						|
      OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
 | 
						|
    if LogOpen then
 | 
						|
      FLogWasEmpty := SetFilePointer(FLogFileHandle, 0, nil, FILE_END) = 0;
 | 
						|
  end
 | 
						|
  else
 | 
						|
    FLogWasEmpty := False;
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
procedure TSimpleExceptionLog.Write(const Text: string; Indent: Integer);
 | 
						|
var
 | 
						|
  S: string;
 | 
						|
  SL: TStringList;
 | 
						|
  I: Integer;
 | 
						|
begin
 | 
						|
  if LogOpen then
 | 
						|
  begin
 | 
						|
    SL := TStringList.Create;
 | 
						|
    try
 | 
						|
      SL.Text := Text;
 | 
						|
      for I := 0 to SL.Count - 1 do
 | 
						|
      begin
 | 
						|
        S := StringOfChar(' ', Indent) + StrEnsureSuffix(AnsiCrLf, TrimRight(SL[I]));
 | 
						|
        FileWrite(Integer(FLogFileHandle), Pointer(S)^, Length(S));
 | 
						|
      end;
 | 
						|
    finally
 | 
						|
      SL.Free;
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
procedure TSimpleExceptionLog.Write(Strings: TStrings; Indent: Integer);
 | 
						|
var
 | 
						|
  I: Integer;
 | 
						|
begin
 | 
						|
  for I := 0 to Strings.Count - 1 do
 | 
						|
    Write(Strings[I], Indent);
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
procedure TSimpleExceptionLog.WriteStamp(SeparatorLen: Integer);
 | 
						|
begin
 | 
						|
  if SeparatorLen = 0 then
 | 
						|
    SeparatorLen := 100;
 | 
						|
  SeparatorLen := Max(SeparatorLen, 20);  
 | 
						|
  OpenLog;
 | 
						|
  if not FLogWasEmpty then
 | 
						|
    Write(AnsiCrLf);
 | 
						|
  Write(StrRepeat('=', SeparatorLen));
 | 
						|
  Write(Format('= %-*s =', [SeparatorLen - 4, DateTimeToStr(Now)]));
 | 
						|
  Write(StrRepeat('=', SeparatorLen));
 | 
						|
end;
 | 
						|
 | 
						|
//==================================================================================================
 | 
						|
// Exception dialog
 | 
						|
//==================================================================================================
 | 
						|
 | 
						|
var
 | 
						|
  ExceptionShowing: Boolean;
 | 
						|
 | 
						|
{ TExceptionDialog }
 | 
						|
 | 
						|
procedure TExceptionDialog.AfterCreateDetails;
 | 
						|
begin
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
procedure TExceptionDialog.BeforeCreateDetails;
 | 
						|
begin
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
procedure TExceptionDialog.CopyReportToClipboard;
 | 
						|
begin
 | 
						|
  ClipBoard.AsText := ReportAsText;
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
procedure TExceptionDialog.CreateDetailInfo;
 | 
						|
begin
 | 
						|
  CreateReport([siStackList, siOsInfo, siModuleList, siActiveControls]);
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
procedure TExceptionDialog.CreateDetails;
 | 
						|
begin
 | 
						|
  Screen.Cursor := crHourGlass;
 | 
						|
  DetailsMemo.Lines.BeginUpdate;
 | 
						|
  try
 | 
						|
    CreateDetailInfo;
 | 
						|
    ReportToLog;
 | 
						|
    DetailsMemo.SelStart := 0;
 | 
						|
    SendMessage(DetailsMemo.Handle, EM_SCROLLCARET, 0, 0);
 | 
						|
    AfterCreateDetails;
 | 
						|
  finally
 | 
						|
    DetailsMemo.Lines.EndUpdate;
 | 
						|
    OkBtn.Enabled := True;
 | 
						|
    DetailsBtn.Enabled := True;
 | 
						|
    OkBtn.SetFocus;
 | 
						|
    Screen.Cursor := crDefault;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
procedure TExceptionDialog.CreateReport(const SystemInfo: TExcDialogSystemInfos);
 | 
						|
const
 | 
						|
  MMXText: array[Boolean] of PChar = ('', 'MMX');
 | 
						|
  FDIVText: array[Boolean] of PChar = (' [FDIV Bug]', '');
 | 
						|
var
 | 
						|
  SL: TStringList;
 | 
						|
  I: Integer;
 | 
						|
  ModuleName: TFileName;
 | 
						|
  CpuInfo: TCpuInfo;
 | 
						|
  C: TWinControl;
 | 
						|
  NtHeaders: PImageNtHeaders;
 | 
						|
  ModuleBase: Cardinal;
 | 
						|
  ImageBaseStr: string;
 | 
						|
  StackList: TJclStackInfoList;
 | 
						|
begin
 | 
						|
  SL := TStringList.Create;
 | 
						|
  try
 | 
						|
    // Stack list
 | 
						|
    if siStackList in SystemInfo then
 | 
						|
    begin
 | 
						|
      StackList := JclLastExceptStackList;
 | 
						|
      if Assigned(StackList) then
 | 
						|
      begin
 | 
						|
        DetailsMemo.Lines.Add(Format(RsStackList, [DateTimeToStr(StackList.TimeStamp)]));
 | 
						|
        StackList.AddToStrings(DetailsMemo.Lines, False, True, True);
 | 
						|
        NextDetailBlock;
 | 
						|
      end;
 | 
						|
    end;
 | 
						|
    // System and OS information
 | 
						|
    if siOsInfo in SystemInfo then
 | 
						|
    begin
 | 
						|
      DetailsMemo.Lines.Add(Format(RsOSVersion, [GetWindowsVersionString, NtProductTypeString,
 | 
						|
        Win32MajorVersion, Win32MinorVersion, Win32BuildNumber, Win32CSDVersion]));
 | 
						|
      GetCpuInfo(CpuInfo);
 | 
						|
      with CpuInfo do
 | 
						|
        DetailsMemo.Lines.Add(Format(RsProcessor, [Manufacturer, CpuName,
 | 
						|
          RoundFrequency(FrequencyInfo.NormFreq),
 | 
						|
          MMXText[MMX], FDIVText[IsFDIVOK]]));
 | 
						|
      DetailsMemo.Lines.Add(Format(RsScreenRes, [Screen.Width, Screen.Height, GetBPP]));
 | 
						|
      NextDetailBlock;
 | 
						|
    end;
 | 
						|
    // Modules list
 | 
						|
    if (siModuleList in SystemInfo) and LoadedModulesList(SL, GetCurrentProcessId) then
 | 
						|
    begin
 | 
						|
      DetailsMemo.Lines.Add(RsModulesList);
 | 
						|
      SL.CustomSort(SortModulesListByAddressCompare);
 | 
						|
      for I := 0 to SL.Count - 1 do
 | 
						|
      begin
 | 
						|
        ModuleName := SL[I];
 | 
						|
        ModuleBase := Cardinal(SL.Objects[I]);
 | 
						|
        DetailsMemo.Lines.Add(Format('[%.8x] %s', [ModuleBase, ModuleName]));
 | 
						|
        NtHeaders := PeMapImgNtHeaders(Pointer(ModuleBase));
 | 
						|
        if (NtHeaders <> nil) and (NtHeaders^.OptionalHeader.ImageBase <> ModuleBase) then
 | 
						|
          ImageBaseStr := Format('<%.8x> ', [NtHeaders^.OptionalHeader.ImageBase])
 | 
						|
        else
 | 
						|
          ImageBaseStr := StrRepeat(' ', 11);
 | 
						|
        if VersionResourceAvailable(ModuleName) then
 | 
						|
          with TJclFileVersionInfo.Create(ModuleName) do
 | 
						|
          try
 | 
						|
            DetailsMemo.Lines.Add(ImageBaseStr + BinFileVersion + ' - ' + FileVersion);
 | 
						|
            if FileDescription <> '' then
 | 
						|
              DetailsMemo.Lines.Add(StrRepeat(' ', 11) + FileDescription);
 | 
						|
          finally
 | 
						|
            Free;
 | 
						|
          end
 | 
						|
        else
 | 
						|
          DetailsMemo.Lines.Add(ImageBaseStr + RsMissingVersionInfo);
 | 
						|
      end;
 | 
						|
      NextDetailBlock;
 | 
						|
    end;
 | 
						|
    // Active controls
 | 
						|
    if (siActiveControls in SystemInfo) and (FLastActiveControl <> nil) then
 | 
						|
    begin
 | 
						|
      DetailsMemo.Lines.Add(RsActiveControl);
 | 
						|
      C := FLastActiveControl;
 | 
						|
      while C <> nil do
 | 
						|
      begin
 | 
						|
        DetailsMemo.Lines.Add(Format('%s "%s"', [C.ClassName, C.Name]));
 | 
						|
        C := C.Parent;
 | 
						|
      end;
 | 
						|
      NextDetailBlock;
 | 
						|
    end;
 | 
						|
  finally
 | 
						|
    SL.Free;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
procedure TExceptionDialog.DetailsBtnClick(Sender: TObject);
 | 
						|
begin
 | 
						|
  DetailsVisible := not DetailsVisible;
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
class procedure TExceptionDialog.ExceptionHandler(Sender: TObject; E: Exception);
 | 
						|
begin
 | 
						|
  if ExceptionShowing then
 | 
						|
    Application.ShowException(E)
 | 
						|
  else
 | 
						|
  begin
 | 
						|
    ExceptionShowing := True;
 | 
						|
    try
 | 
						|
      ShowException(E, nil);
 | 
						|
    finally
 | 
						|
      ExceptionShowing := False;
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
class procedure TExceptionDialog.ExceptionThreadHandler(Thread: TJclDebugThread);
 | 
						|
begin
 | 
						|
  if ExceptionShowing then
 | 
						|
    Application.ShowException(Thread.SyncException)
 | 
						|
  else
 | 
						|
  begin
 | 
						|
    ExceptionShowing := True;
 | 
						|
    try
 | 
						|
      ShowException(Thread.SyncException, Thread);
 | 
						|
    finally
 | 
						|
      ExceptionShowing := False;
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
procedure TExceptionDialog.FormCreate(Sender: TObject);
 | 
						|
begin
 | 
						|
  FSimpleLog := TSimpleExceptionLog.Create;
 | 
						|
  FFullHeight := ClientHeight;
 | 
						|
  DetailsVisible := False;
 | 
						|
  Caption := Format(RsAppError, [Application.Title]);
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
procedure TExceptionDialog.FormDestroy(Sender: TObject);
 | 
						|
begin
 | 
						|
  FreeAndNil(FSimpleLog);
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
procedure TExceptionDialog.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
 | 
						|
begin
 | 
						|
  if (Key = Ord('C')) and (ssCtrl in Shift) then
 | 
						|
  begin
 | 
						|
    CopyReportToClipboard;
 | 
						|
    MessageBeep(MB_OK);
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
procedure TExceptionDialog.FormPaint(Sender: TObject);
 | 
						|
begin
 | 
						|
  DrawIcon(Canvas.Handle, TextLabel.Left - GetSystemMetrics(SM_CXICON) - 15,
 | 
						|
    TextLabel.Top, LoadIcon(0, IDI_ERROR));
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
procedure TExceptionDialog.FormResize(Sender: TObject);
 | 
						|
begin
 | 
						|
  UpdateTextLabelScrollbars;
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
procedure TExceptionDialog.FormShow(Sender: TObject);
 | 
						|
begin
 | 
						|
  BeforeCreateDetails;
 | 
						|
  MessageBeep(MB_ICONERROR);
 | 
						|
  if FIsMainThead and (GetWindowThreadProcessId(Handle, nil) = MainThreadID) then
 | 
						|
    PostMessage(Handle, UM_CREATEDETAILS, 0, 0)
 | 
						|
  else
 | 
						|
    CreateDetails;
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
function TExceptionDialog.GetReportAsText: string;
 | 
						|
begin
 | 
						|
  Result := StrEnsureSuffix(AnsiCrLf, TextLabel.Text) + AnsiCrLf + DetailsMemo.Text;
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
procedure TExceptionDialog.NextDetailBlock;
 | 
						|
begin
 | 
						|
  DetailsMemo.Lines.Add(StrRepeat(ReportNewBlockDelimiterChar, ReportMaxColumns));
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
function TExceptionDialog.ReportMaxColumns: Integer;
 | 
						|
begin
 | 
						|
  Result := 100;
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
function TExceptionDialog.ReportNewBlockDelimiterChar: Char;
 | 
						|
begin
 | 
						|
  Result := '-';
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
procedure TExceptionDialog.ReportToLog;
 | 
						|
begin
 | 
						|
  if Tag and ReportToLogEnabled <> 0 then
 | 
						|
  begin
 | 
						|
    FSimpleLog.WriteStamp(ReportMaxColumns);
 | 
						|
    try
 | 
						|
      FSimpleLog.Write(ReportAsText);
 | 
						|
    finally
 | 
						|
      FSimpleLog.CloseLog;
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
procedure TExceptionDialog.SetDetailsVisible(const Value: Boolean);
 | 
						|
var
 | 
						|
  DetailsCaption: string;
 | 
						|
begin
 | 
						|
  FDetailsVisible := Value;
 | 
						|
  DetailsCaption := Trim(StrRemoveChars(DetailsBtn.Caption, ['<', '>']));
 | 
						|
  if Value then
 | 
						|
  begin
 | 
						|
    Constraints.MinHeight := FNonDetailsHeight + 100;
 | 
						|
    Constraints.MaxHeight := Screen.Height;
 | 
						|
    DetailsCaption := '<< ' + DetailsCaption;
 | 
						|
    ClientHeight := FFullHeight;
 | 
						|
    DetailsMemo.Height := FFullHeight - DetailsMemo.Top - 3;
 | 
						|
  end
 | 
						|
  else
 | 
						|
  begin
 | 
						|
    FFullHeight := ClientHeight;
 | 
						|
    DetailsCaption := DetailsCaption + ' >>';
 | 
						|
    if FNonDetailsHeight = 0 then
 | 
						|
    begin
 | 
						|
      ClientHeight := Bevel1.Top;
 | 
						|
      FNonDetailsHeight := Height;
 | 
						|
    end
 | 
						|
    else
 | 
						|
      Height := FNonDetailsHeight;
 | 
						|
    Constraints.MinHeight := FNonDetailsHeight;
 | 
						|
    Constraints.MaxHeight := FNonDetailsHeight
 | 
						|
  end;
 | 
						|
  DetailsBtn.Caption := DetailsCaption;
 | 
						|
  DetailsMemo.Enabled := Value;
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
class procedure TExceptionDialog.ShowException(E: Exception; Thread: TJclDebugThread);
 | 
						|
begin
 | 
						|
  if ExceptionDialog = nil then
 | 
						|
    ExceptionDialog := ExceptionDialogClass.Create(Application);
 | 
						|
  try
 | 
						|
    with ExceptionDialog do
 | 
						|
    begin
 | 
						|
      FIsMainThead := (GetCurrentThreadId = MainThreadID);
 | 
						|
      FLastActiveControl := Screen.ActiveControl;
 | 
						|
      TextLabel.Text := AdjustLineBreaks(StrEnsureSuffix('.', E.Message));
 | 
						|
      UpdateTextLabelScrollbars;
 | 
						|
      DetailsMemo.Lines.Add(Format(RsExceptionClass, [E.ClassName]));
 | 
						|
      if Thread = nil then
 | 
						|
        DetailsMemo.Lines.Add(Format(RsExceptionAddr, [ExceptAddr]))
 | 
						|
      else
 | 
						|
        DetailsMemo.Lines.Add(Format(RsThread, [Thread.ThreadInfo]));
 | 
						|
      NextDetailBlock;
 | 
						|
      ShowModal;
 | 
						|
    end;
 | 
						|
  finally
 | 
						|
    FreeAndNil(ExceptionDialog);
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
procedure TExceptionDialog.UMCreateDetails(var Message: TMessage);
 | 
						|
begin
 | 
						|
  Update;
 | 
						|
  CreateDetails;
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
procedure TExceptionDialog.UpdateTextLabelScrollbars;
 | 
						|
begin
 | 
						|
  if Tag and DisableTextScrollbar = 0 then
 | 
						|
  begin
 | 
						|
    Canvas.Font := TextLabel.Font;
 | 
						|
    if TextLabel.Lines.Count * Canvas.TextHeight('Wg') > TextLabel.ClientHeight then
 | 
						|
      TextLabel.ScrollBars := ssVertical
 | 
						|
    else
 | 
						|
      TextLabel.ScrollBars := ssNone;
 | 
						|
   end;   
 | 
						|
end;
 | 
						|
 | 
						|
//==================================================================================================
 | 
						|
// Exception handler initialization code
 | 
						|
//==================================================================================================
 | 
						|
 | 
						|
procedure InitializeHandler;
 | 
						|
begin
 | 
						|
  JclStackTrackingOptions := JclStackTrackingOptions + [stRawMode];
 | 
						|
  {$IFNDEF HOOK_DLL_EXCEPTIONS}
 | 
						|
  JclStackTrackingOptions := JclStackTrackingOptions + [stStaticModuleList];
 | 
						|
  {$ENDIF HOOK_DLL_EXCEPTIONS}
 | 
						|
  JclDebugThreadList.OnSyncException := TExceptionDialog.ExceptionThreadHandler;
 | 
						|
  JclStartExceptionTracking;
 | 
						|
  {$IFDEF HOOK_DLL_EXCEPTIONS}
 | 
						|
  if HookTApplicationHandleException then
 | 
						|
    JclTrackExceptionsFromLibraries;
 | 
						|
  {$ENDIF HOOK_DLL_EXCEPTIONS}
 | 
						|
  Application.OnException := TExceptionDialog.ExceptionHandler;
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
procedure UnInitializeHandler;
 | 
						|
begin
 | 
						|
  Application.OnException := nil;
 | 
						|
  JclDebugThreadList.OnSyncException := nil;
 | 
						|
  JclUnhookExceptions;
 | 
						|
  JclStopExceptionTracking;
 | 
						|
end;
 | 
						|
 | 
						|
//--------------------------------------------------------------------------------------------------
 | 
						|
 | 
						|
initialization
 | 
						|
  {$IFDEF EXCEPTION_DEBUG}
 | 
						|
  InitializeHandler;
 | 
						|
  {$ENDIF}
 | 
						|
 | 
						|
finalization
 | 
						|
  {$IFDEF EXCEPTION_DEBUG}
 | 
						|
  UnInitializeHandler;
 | 
						|
  {$ENDIF}
 | 
						|
 | 
						|
end.
 |