The unit "madExcept" implements all the exception handling stuff. A full
list of what is contained in this unit can be found in the
madExcept Unit Reference.
When an exception occurs, madExcept creates an interface named
"IMEException", which not only contains all the information about the
exception, but which inherits the madExcept settings from the module,
in which the exception occurred. In your own exception callbacks you can
then change the settings for the current exception only, if you like, by
accessing the properties which the "IMEException" interface inherited from
the IMESettings interface.
 |
type
IMEException = interface (IMESettings) ['{5EE7E49B-571F-4504-B9E6-1EEF6587C51F}'];
|
|
The "IMEException" interface contains information about the current state of
the exception:
 |
type
TExceptPhase = (epNotHandledYet,
epQuickFiltering,
epMainPhase,
epPostProcessing,
epCompleteReport
);
property IMEException. Phase : TExceptPhase;
property IMEException. CanContinue : boolean;
|
|
There are lots of properties which give you more information about the
exception itself and about the circumstances in which it occurred:
 |
type
TExceptType = (etNormal, etFrozen, etHidden);
TExceptSource = (esManual,
esAntiFreezeCheck,
esBcbAbnormalProgramTermination,
esSysUtilsShowException,
esApplicationShowException,
esApplicationHandleException,
esCGI,
esISAPI,
esHttpExtension,
esIntraweb,
esTThreadSynchronize,
esRuntimeError,
esInitializePackage,
esExceptProc,
esSystemExceptionHandler,
esHidden,
esThreadFrame,
esTThreadExecute,
esUnhandledFilter);
property IMEException. ExceptType : TExceptType;
property IMEException. ExceptObject : TObject;
property IMEException. ExceptClass : string;
property IMEException. ExceptMessage : string;
property IMEException. ExceptAddr : pointer;
property IMEException. CrashedThreadId : dword;
property IMEException. Context : PContext;
property IMEException. ExceptionRecord : TExceptionRecord;
property IMEException. Source : TExceptSource;
property IMEException. RelatedObject : TObject;
property IMEException. Package : dword;
property IMEException. BugReportHeader : IMEFields;
property IMEException. BugReportSections : IMEFields;
property IMEException. ScreenShot : INVBitmap;
property IMEException. ThreadIds [index: integer] : dword;
property IMEException. Callstacks [index: integer] : TStackTrace;
|
|
There are 2 ways to ask the composed bug report text. You can either ask the
"IMEException.BugReport" property. Or you can call the
"IMEException.GetBugReport" method. If you want to understand the
difference, you first need to understand, that madExcept calculates
the bug report in a background thread, while already calling your registered
exception handlers at the same time. As long as you behave nicely, this
logic works quite fine and results in that the exception box can be shown
without delay, although the calculation of the full bug report might not be
fully done yet.
However, if you ask "IMEException.BugReport" or if you call
"IMEException.GetBugReport(true)", then you force madExcept to
complete the full bug report composition, before it can return to you. When
doing this in your registered exception handler you might delay the showing
of the exception box and you might see a progress bar window instead. You
can get around that by calling "IMEException.GetBugReport(false)" instead.
In that case you'll get a bug report without delaying anything. However, the
bug report may not be fully rendered yet.
Generally you should avoid asking "IMEException.BugReport" or calling
"IMEException.GetBugReport(true)" in your exception handler. If you
absolutely do need access to the complete bug report text, you
should when calling RegisterExceptionHandler explicitly ask for being
called when the exception phase is "epCompleteReport". This way you don't
delay anything, however, the exception box is likely to be shown before your
exception callback is called. Hey - you can't have it all. But at least you
have the choice.
 |
function IMEException. GetBugReport (mustBeComplete: boolean = true) : string;
property IMEException. BugReport : string;
|
|
In some situations you might want to check whether the current exception
is already stored somewhere. Comparing exceptions is not too easy, though.
Especially comparing callstacks is tough. So madExcept offers you
some help by adding a "CallstackCrc" property to the "IMEException"
interface. It consists of 3 dwords. The first dword designates the exception
location only. The second dword is a crc of the callstack of the crashed
thread. The third dword is a crc of the callstack of all delphi threads.
Please note that this information is only available after all the callstacks
were fully calculated. So asking this property before the "epCompleteReport"
phase doesn't make too much sense.
 |
property IMEException. CallstackCrc [index: integer] : dword;
|
|
As already explained, the composition of the bug report is done in
background. If you want to be notified about any progress the composition
makes, you can register a callback function. The "critical" parameter
states if your callback absolutely does need to get the complete bug report.
If not, and if the user aborts the exception box before the rendering of the
bug report is complete, the rendering can be aborted to save processing
time.
 |
type
TBugReportCallback = procedure (complete: boolean; bugReport: string; exceptIntf: IMEException);
TBugReportCallbackOO = procedure (complete: boolean; bugReport: string; exceptIntf: IMEException) of object;
procedure IMEException. RegisterBugReportCallback (bugReportCallback: TBugReportCallback; critical: boolean); overload;
procedure IMEException. RegisterBugReportCallback (bugReportCallback: TBugReportCallbackOO; critical: boolean); overload;
procedure IMEException. UnregisterBugReportCallback (bugReportCallback: TBugReportCallback ); overload;
procedure IMEException. UnregisterBugReportCallback (bugReportCallback: TBugReportCallbackOO); overload;
|
|
Everytime you change the bug report header or the bug report sections,
callbacks registered through IMEException.RegisterBugReportCallback
are called, which can result in follow up actions. If you are doing multiple
changes to the bug report header and/or sections, you should call
"IMEException.BeginUpdate" before and "IMEException.EndUpdate" after doing
your changes. This will make sure that the callbacks are fired only once.
 |
procedure IMEException. BeginUpdate;
procedure IMEException. EndUpdate;
|
|
There are some settings stored in the "IMEException" interface, which you
can change, which however only have effect during the "epQuickFiltering"
phase:
 |
property IMEException. CreateBugReport : boolean;
property IMEException. CreateScreenShot : boolean;
property IMEException. CallHandlers : boolean;
|
|
The following settings can be changed and show effect during all phases of
exception handling:
 |
property IMEException. AppendScreenShot : boolean;
property IMEException. ShowSetting : TMEShowSetting;
property IMEException. ShowAssistant : string;
|
|
You can force an exception box to be shown by calling "IMEException.Show".
But actually this makes sense only, if you've manually created the
exception by calling NewException. Otherwise you usually shouldn't
call this method.
 |
procedure IMEException. Show;
|
|
Calling one of the following methods has the same effect as the end user
pressing one of the buttons in the exception box. Calling "ShowBugReport"
only has any effect if the exception box is actually currently visible.
The other methods work even if the exception box is not currently visible.
If you leave the "parentWindow" parameter zero, madExcept will use its own
exception box main window handle as the parent window.
 |
procedure IMEException. ShowBugReport (parentWindow: dword = 0);
procedure IMEException. SendBugReport (parentWindow: dword = 0);
procedure IMEException. SaveBugReport (parentWindow: dword = 0);
procedure IMEException. PrintBugReport (parentWindow: dword = 0);
procedure IMEException. ContinueApplication (parentWindow: dword = 0);
procedure IMEException. RestartApplication (parentWindow: dword = 0);
procedure IMEException. CloseApplication (parentWindow: dword = 0);
|
|
You can manually create an "IMEException" interface. You can do that if you
want to get a bug report, or if you want to show an exception box. You can
ignore most parameters. If you just want to have a snapshot of the current
situation (makes sense e.g. if the application seems to be frozen), just
leave all parameters empty.
 |
function NewException (exceptType : TExceptType = etFrozen;
exceptObject : TObject = nil;
exceptAddr : pointer = nil;
canContinue : boolean = true;
crashedThreadId : dword = 0;
currentEsp : dword = 0;
currentEbp : dword = 0;
context : PContext = nil;
settings : IMEModuleSettings = nil;
source : TExceptSource = esManual;
relatedObject : TObject = nil;
package : dword = 0;
preparedStack : pointer = nil;
dontCompleteYet : boolean = false ) : IMEException;
|
|
If you got a bug report from somewhere in text form, you can turn it into
an "IMEException" interface by calling "ImportBugReport". Please note that
many properties won't work properly, e.g. IMEException.ExceptObject.
The main purpose of this function is to show the bug report with the
standard madExcept dialog.
 |
function ImportBugReport (const bugReport: UnicodeString) : IMEException;
ImportBugReport(someBugReport).Show;
|
|
The function "HandleException" does all the stuff like firing the registered
exception handlers and showing the exception box. If you want your dlls to
handle their own exceptions, you should put a "try..except" frame around
every exported function and around every message handler.
 |
procedure HandleException (exceptType : TExceptType = etNormal;
exceptObject : TObject = nil;
exceptAddr : pointer = nil;
canContinue : boolean = true;
currentEsp : dword = 0;
currentEbp : dword = 0;
context : PContext = nil;
source : TExceptSource = esManual;
relatedObject : TObject = nil;
package : dword = 0;
showReport : TPString = nil );
library blabla;
uses madExcept, SysUtils;
procedure DllExportedFunction;
begin
try
except
HandleException;
end;
end;
exports DllExportedFunction;
end.
|
|
When using the option "pause running delphi/bcb threads", madExcept
automatically suspends all delphi/bcb threads in the moment when they call
GetMessage or PeekMessage. If you have a thread which you want to be
paused during exception handling, but which doesn't call GetMessage nor
PeekMessage, you can call "PauseMeEventually" in the thread's main loop.
Calling this function doesn't harm anywhere. It just checks whether the
current thread should be suspended and in that case suspends it. You can add
it to your threads without danger.
 |
procedure PauseMeEventually;
function SomeThread(param: pointer) : integer; stdcall;
begin
while ReadFile(...) do begin
PauseMeEventually;
end;
end;
|
|
You can register exception handlers, which then get called for every
exception that occurs thereafter. There's no limit, you can register as many
exception handlers as you like. Each one is called in a row, until one sets
"handled" to true. For each exception handler you can choose whether you
want madExcept to try to call your handler in the context of the main
thread - and what shall happen if the main thread doesn't respond (see
"TSyncType"). Furthermore you can choose in what phase of exception handling
you want your handler to be called (see "TExceptPhase").
 |
type
TSyncType = (stDontSync, stTrySyncCallOnSuccess, stTrySyncCallAlways);
TExceptEvent = procedure (const exceptIntf : IMEException;
var handled : boolean);
TExceptEventOO = procedure (const exceptIntf : IMEException;
var handled : boolean) of object;
procedure RegisterExceptionHandler (exceptHandler: TExceptEvent;
sync : TSyncType;
phase : TExceptPhase = epMainPhase); overload;
procedure RegisterExceptionHandler (exceptHandler: TExceptEventOO;
sync : TSyncType;
phase : TExceptPhase = epMainPhase); overload;
function UnregisterExceptionHandler (exceptHandler: TExceptEvent ) : boolean; overload;
function UnregisterExceptionHandler (exceptHandler: TExceptEventOO) : boolean; overload;
|
|
The following "exception action handlers" get called whenever action happens
as a result of an exception. You can register as many exception action
handlers as you like. Each one is called in a row. If the action is
"eaSendBugReport", "eaSaveBugReport" or "eaPrintBugReport" and a handler
sets the parameter "handled" (for all other actions this parameter has no
meaning) to true, madExcept doesn't send/save/print the bug report
anymore, it's your duty then. Furthermore in that case the handler calling
row is canceled.
 |
type
TExceptAction = (eaSendBugReport,
eaSaveBugReport,
eaPrintBugReport,
eaSendBugReport2,
eaSaveBugReport2,
eaPrintBugReport2,
eaSendBugReport3,
eaSaveBugReport3,
eaPrintBugReport3,
eaShowBugReport,
eaHelp,
eaContinueApplication,
eaRestartApplication,
eaCloseApplication);
TExceptActionEvent = procedure (action : TExceptAction;
const exceptIntf : IMEException;
var handled : boolean );
TExceptActionEventOO = procedure (action : TExceptAction;
const exceptIntf : IMEException;
var handled : boolean ) of object;
procedure RegisterExceptActionHandler (actionHandler: TExceptActionEvent;
sync : TSyncType ); overload;
procedure RegisterExceptActionHandler (actionHandler: TExceptActionEventOO;
sync : TSyncType ); overload;
function UnregisterExceptActionHandler (actionHandler: TExceptActionEvent ) : boolean; overload;
function UnregisterExceptActionHandler (actionHandler: TExceptActionEventOO) : boolean; overload;
|
|
Sometimes you want to be notified even about exceptions, which are handled
by a try..except statement somewhere. Normally such exceptions are hidden
from you and the user. But in a kind of debug mode there might be situations
where you want to know about such exceptions, too. With the following
functions you can tell madExcept to catch such "hidden exceptions" for you
and notify you about them. Please note, however, that this makes sense only
for debugging. You should not enable this feature by default in a shipping
product, because otherwise this option does cost time during the normal
program flow, while all the other features of madExcept cost time only when
a "real" exception occurs.
When a hidden exception occurs, your handler will be called with "handled"
initialized to "true". That means if you don't do anything, you're only
notified about the exception, but nothing more happens. If you want the
exception to behave like normal exceptions, just set "handled" to "false".
You can ask IMEException.BugReport, but doing so in the hidden
exception handler will result in a delay, because the bug report will be
calculated in the very moment you access the "BugReport" property.
 |
procedure RegisterHiddenExceptionHandler (hiddenHandler: TExceptEvent;
sync : TSyncType ); overload;
procedure RegisterHiddenExceptionHandler (hiddenHandler: TExceptEventOO;
sync : TSyncType ); overload;
function UnregisterHiddenExceptionHandler (hiddenHandler: TExceptEvent ) : boolean; overload;
function UnregisterHiddenExceptionHandler (hiddenHandler: TExceptEventOO) : boolean; overload;
|
|
The madExcept exception box is always on top. Sometimes inside of an
exception handler you may want to show other windows or start other programs.
In such a situation it might make sense to temporarily remove the topmost
state of the exception box. The following two functions help you do that.
 |
function GetExceptBoxHandle : dword;
function SetTopmost (window: dword; topmost: boolean) : boolean;
|
|
You can pause madExcept's exception catching powers, if you like. But please
don't call this from multiple threads at the same time. Also please don't
turn it on and off again all the time. It should work ok, but it's not fully
thread safe, so be careful. Each "PauseMadExcept(true)" must have its own
"PauseMadExcept(false)" call.
 |
procedure PauseMadExcept (pause: boolean = true);
|
|
With the following function you can influence freeze checking.
"PauseFreeCheck" manually pauses & resumes freeze checking. You should use
it for code blocks where the main thread is doing time consuming tasks
without handling messages. Otherwise you're in danger of getting false
freezing alarms. Also you can call "ImNotFrozen" at any time to reset the
freeze check timer. Furthermore you can change the timeout value by calling
"SetFreezeTimeout".
 |
procedure PauseFreezeCheck (pause: boolean = true);
procedure ImNotFrozen;
procedure SetFreezeTimeout (newTimeout: dword);
|
|
Leak checking is normally turned on/off globally in the madExcept settings
dialog. However, you can also activate it manually, even for specific
threads, only, if you prefer it that way. Furthermore you can also
explicitly tell madExcept to not report a specific handle or pointer as a
leak. Doing so makes sense if you have an "intentional" leak somewhere or
madExcept gets confused about a specific handle or pointer for some strange
reason.
 |
procedure StartLeakChecking (limitedToCurrentThread: boolean = true);
procedure StopLeakChecking (limitedToCurrentThread: boolean = true);
procedure ReportLeaksNow (limitedToCurrentThread: boolean = true);
function GetLeakReport (limitedToCurrentThread: boolean = true; includeCallstacks: boolean = true; hideChildLeaks: boolean = true) : UnicodeString;
function GetLeakCallstack (allocationNumber: int64) : UnicodeString;
procedure ClearLeaks (limitedToCurrentThread: boolean = true; rememberLeakParents: boolean = true);
function ShowLeakReport (const leakReport: UnicodeString) : boolean;
function HideLeak (memory: pointer) : boolean; overload;
function HideLeak (handle: THandle) : boolean; overload;
function HideLeak (someClass: TClass; count: integer) : boolean; overload;
function HideLeak (const callstack: UnicodeString) : boolean; overload;
function HideInitializationLeaks : boolean;
procedure SetLeakReportFile (const leakReportFile: UnicodeString);
procedure ShowLeakProgressWindow (show: boolean);
procedure ShowNoLeaksWindow (show: boolean);
procedure SetChildLeakFiltering (doFiltering: boolean);
procedure SetDebugMmAlignment (alignment: integer = 4);
procedure WaitForCleanExitProcess (wait: boolean);
|
|
If your project uses multiple threads, bug reports usually contain the
callstacks of all threads. The callstack of the main thread shows up under
the name "main thread", of course. But the callstacks of the other threads
are named only with either "thread $[id]" (pure win32 threads created by
CreateThread or BeginThread) or with their TThread class name. As a result
it's often difficult to find out, which callstack belongs to which of your
secondary threads. To make the bug report better understandable you can give
all your threads a name with the function "NameThread". If you want to
exclude specific threads from the bug report, call "NameThread" with the
name "-".
 |
procedure NameThread (threadID: dword; threadName: string);
function GetThreadName (threadID: dword = 0) : string;
|
|
When writing NT/2k/XP services, you will notice that the anti freeze detection
doesn't work. In fact it brings false alarms all the time. The reason is that
in NT/2k/XP services the main thread is sleeping for the most part and doesn't
handle messages at all. The main work is done in a seperate thread.
In case you want anti freeze detection for that worker thread, you can't use
the madExcept settings dialog to turn on
anti freeze detection, instead turn it off there and call "InitAntiFreeze" at
the beginning of your worker thread.
 |
procedure InitAntiFreeze;
procedure CloseAntiFreeze;
|
|
Sometimes there are situations where you might want to catch exceptions in a
try..except statement. Nevertheless you might want to get a full bug report
in the except..end block. That's possible by manually calling the function
"CreateBugReport", which creates a full bug report according to your
madExcept settings.
 |
function CreateBugReport (exceptType : TExceptType = etFrozen;
exceptObject : TObject = nil;
exceptAddr : pointer = nil;
callingThreadId : dword = 0;
currentEsp : dword = 0;
currentEbp : dword = 0;
context : PContext = nil;
showPleaseWaitBox : boolean = false;
settings : IMEModuleSettings = nil;
source : TExceptSource = esManual;
relatedObject : TObject = nil;
package : dword = 0;
preparedStack : pointer = nil ) : string;
|
|
If you need a stack trace for one specific thread, only, you can call one
of the following two functions. Generally you can leave all parameters
unchanged, unless you have special needs:
 |
function GetCrashStackTrace (hideUglyItems : boolean = false;
showRelativeAddrs : boolean = false;
showRelativeLines : boolean = false;
stackTrace : TPStackTrace = nil;
const progressAlert : IProgressAlert = nil;
dumbTrace : boolean = false;
pCrc1 : TPCardinal = nil;
pCrc2 : TPCardinal = nil;
pDelphiThread : TPBoolean = nil;
pMinDebugInfos : TPDAUnicodeString = nil ) : string;
function GetThreadStackTrace (threadId : dword = 0;
hideUglyItems : boolean = false;
showRelativeAddrs : boolean = false;
showRelativeLines : boolean = false;
stackTrace : TPStackTrace = nil;
const progressAlert : IProgressAlert = nil;
dumbTrace : boolean = false;
pCrc1 : TPCardinal = nil;
pCrc2 : TPCardinal = nil;
pDelphiThread : TPBoolean = nil;
pMinDebugInfos : TPDAUnicodeString = nil ) : string;
|
|
In the bug report you'll find a whole collection of useful system
information. If you want to use the same information in your own functions,
you can do so by calling the following functions:
 |
function IsUserAdmin : boolean;
function GetTSClientName : string;
function GetOsVersionString : string;
function GetOsLanguageString : string;
function GetSystemUpTime : string;
function GetProgramUpTime : string;
function GetCpuName : string;
function GetCpuCount : integer;
function GetMemoryStatus : string;
function Get9xResourceReport : string;
function GetDisplayModeString : string;
function GetAllocatedMemory : string;
function GetLargestFreeBlock : string;
|
|
Finally you can call all the action functions, which are executed, when a
user hits one of the exception box buttons. This way you can easily create
your own exception box without having to reimplement all the functionality
like mailing and printing etc...
 |
function SendBugReport (bugReport : string;
screenShot : INVBitmap;
parentWindow : dword = 0;
settings : IMESettings = nil) : TExtBool;
function SaveBugReport (bugReport : string;
parentWindow : dword = 0;
settings : IMESettings = nil) : boolean;
function PrintBugReport (bugReport : string;
parentWindow : dword = 0;
settings : IMESettings = nil) : boolean;
procedure RestartApplication;
procedure CloseApplication;
function AutoSendBugReport (bugReport : string;
screenShot : INVBitmap;
settings : IMESettings = nil) : TExtBool;
function AutoSaveBugReport (bugReport : string;
settings : IMESettings = nil) : boolean;
|
|