Scope of Application.OnException AND Delphi - delphi

I am a new programmer and trying to understand how Delphi's Application.OnException event works. A colleague has modified the default exception handling by creating his own method and instantiating it and passing it to Application.OnException in the initialization section of a unit. This unit is declared in the uses clause of another unit and otherwise unused.
unit ADLDebug;
...
class procedure TADLExceptionHandler.ADLHandleException (Sender: TObject; E: Exception);
...
initialization
Handler := TADLExceptionHandler.Create;
Application.OnException := Handler.ADLHandleException;
I can only step into the initialization section using the debugger, and not into the ADLHandleException method. I am trying to cause an exception in the code that will be caught by the redefined HandleException method.
Should the scope of the redefined HandleException method be only in the units that include ADLDebug? I was thinking that it should be application wide, but I can't seem to call it.

The scope of Application.OnException is indeed application wide. The event will fire whenever an exception is raised that is not handled.
You are failing to see the event fire because you are raising the exception at startup, before the message loop starts.

Related

Capture exception in Delphi Application.OnException before try except block

I want to log every exception raised in the delphi application.
For that, I overrided the Application.OnException event with one of my own in the project source code.
program Project;
uses
Vcl.Forms,
Unit1 in 'Unit1.pas' {Form1},
Logger in 'Logger.pas',
JCLDebugHandler in 'JCLDebugHandler.pas';
{$R *.res}
begin
Application.Initialize;
Application.OnException := TApplicationException.AppException;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
This works perfectly, but I´m failing at catching with this solution the exceptions catched in a try-except block.
When the exception is catched in the except block, it just not triggers the Application.OnException event.
Is there any way to first catch it in the Application.OnException event rather than the except block?
The Application.OnException handler is only called for unhandled exceptions.
An unhandled exception is one where no try..except block has caught the exception or where it has been caught and then re-raised.
Using some trivial examples to demonstrate, let's assume that this is virtually the only code in the application and that there are no other exception handlers...
try
a := 42 / 0;
except
on EDivisionByZero do
begin
Log.i('Silly division by zero error has been logged');
raise;
end;
end;
In this case, the exception is caught but the application has no strategy for handling the exception so simply logs that it has occurred and then re-raises the exception. Execution will continue in any outer except block. If there are none, or if any that may exist also re-raise the exception, then eventually the exception will reach the Application.OnException handler.
But an exception handler might not need to re-raise an exception:
try
a := 42 / 0;
except
on EDivisionByZero do
a := 0;
end;
In this case, the exception handler handles the division by zero and does not re-raise it since the code is happy to proceed with a result of zero in that case (unlikely, but this is just an example).
Since the exception is not re-raised, execution proceeds (after the try..except block) as if the exception had never happened in the first place. Your Application.OnException will never know about it.
In summary: Application.OnException is your last chance to deal with an unhandled exception. It is not the first opportunity to respond to any exception.
Intercepting exceptions at the moment that they occur, before any application code has had an opportunity to react or deal with them is possible, but is pretty advanced stuff and no simple mechanism is provided "out of the box".
Fortunately there are 3rd party libraries you can use that may provide the capabilities you are seeking to introduce into your application.
A popular one for Delphi which you might wish to check out is madExcept.
I finally used JCL for capturing and logging all my exceptions. I created a new .pas file with the following code:
/// <summary>
/// Inicializa JCL para capturar la informacion necesaria de la excepcion.
/// </summary>
initialization
JclAddExceptNotifier(SetExceptionError);
JclStackTrackingOptions := JclStackTrackingOptions + [stExceptFrame];
JclStartExceptionTracking;
/// <summary>
/// Finaliza la notificacion de JCL luego de capturar la informacion necesaria de la excepcion.
/// </summary>
finalization
JclRemoveExceptNotifier(SetExceptionError);
end.
With this code, I can capture the exception and treat it in my function SetExceptionError. I was avoiding using some 3rd party framework for this simple logging.

Delphi 7 Dunit checks after StopExpectingException are not working as I expect

The below code works fine, the calc... generates an exception, comment it out or change calc... to not throw and exception and the test fails.
StartExpectingException(exception);
calcMembersPIPEndDate(EncodeDate(2005,01,01),true);
StopExpectingException('calcMembersPIPEndDate - 1st after aDay');
My problem is that any checks I put in this test method after this do not execute.
so
checkEquals(1,0);
StartExpectingException(exception);
calcMembersPIPEndDate(EncodeDate(2005,01,01),true);
StopExpectingException('calcMembersPIPEndDate - 1st after aDay');
fails on the 1st checkEquals
StartExpectingException(exception);
calcMembersPIPEndDate(EncodeDate(2005,01,01),true);
StopExpectingException('calcMembersPIPEndDate - 1st after aDay');
checkEquals(1,0);
passes - why?
I have tried to work out what version of Dunit I am using:
testframework.pas has the following - which didn't seem to
rcs_id: string = '#(#)$Id: TestFramework.pas,v 1.117 2006/07/19 02:45:55
rcs_version : string = '$Revision: 1.117 $';
versioninfo.inc
ReleaseNo : array[1..3] of Integer
= (9,2,1);
ReleaseStr = '9.2.1';
ReleaseWhen : array[1..6] of Integer
= (2005,09,25,17,30,00);
These two methods, StartExpectingException and StopExpectingException are not meant to be called directly.
Instead you are supposed to use the ExpectedException property. When you set this property, StartExpectingException is called. Whilst you could call StartExpectingException I belive that the intended usage is that you assign to ExpectedException.
As for StopExpectingException, you don't call it. The framework calls it. It does so in TTestCase.RunTest, the framework code that executes your test method.
So your test case code might look like this:
ExpectedException := ESomeException;
raise ESomeException.Create(...);
When you state that you are expecting an exception, what you are saying is that your test method will raise that exception. Since raising an exception alters control flow, code that appears after the exception is raised will not execute. Exceptions propagate up the call stack until they are caught. The framework will catch the exception in TTestCase.RunTest. If you have indicated that the caught exception is expected then the test will pass, otherwise failure is recorded.
The net result of all this is that the ExpectedException mechanism can be used if the final act of the test method is to raise that expected exception. The ExpectedException mechanism is no use at all if you want to perform further tests after the exception is raised. If you wish to do that then you should either:
Write your own exception handling code, in your test method, that checks that exceptions are raised as designed.
Use CheckException.
StopExpectingException cannot work the way you expect. It's important to understand the flow of execution in an exception state to see why.
Consider the following code:
procedure InnerStep(ARaiseException);
begin
Writeln('Begin');
if ARaiseException then
raise Exception.Create('Watch what happens now');
Writeln('End');
end;
procedure OuterStep;
begin
try
InnerStep(False); //1
InnerStep(True); //2
InnerStep(False); //3
except
//Do something because of exception
raise;
end;
end;
When you call OuterStep above, line //2 will raise an exception inside InnerStep. Now whenever an exception is raised:
The instruction pointer jumps out of each method (a little like goto) to the first except or finally block found in the call-stack.
Writeln('End'); will not be called.
Line //3 will not be called.
Whatever code exists in the except block of OuterStep is executed next.
And finally when raise; is called, the exception is re-raised and the instruction pointer jumps to the next except or finally block.
Note also that like raise; any other exception within the except block will also jump out, (effectively hiding the first exception).
So when you write:
StartExpectingException(...);
DoSomething();
StopExpectingException(...);
There are 2 possibilities:
DoSomething raises an exception and StopExpectingException is never called.
DoSomething doesn't raise an exception and when StopExpectingException is called there is no exception.
David has explained that the DUnit framework calls StopExpectingException for you. But you may be wondering how to approach your test case checking multiple exception scenarios.
Option 1
Write smaller tests.
You know that's what everyone says you're supposed to do in any case right? :)
E.g.
procedure MyTests.TestBadCase1;
begin
ExpectedException := ESomethingBadHappened;
DoSomething('Bad1');
//Nothing to do. Exception should be raised, so any more code would
//be pointless.
//If exception is NOT raised, test will exit 'normally', and
//framework will fail the test when it detects that the expected
//exception was not raised.
end;
procedure MyTests.TestBadCase2;
begin
ExpectedException := ESomethingBadHappened;
DoSomething('Bad2');
end;
procedure MyTests.TestGoodCase;
begin
DoSomething('Good');
//Good case does not (or should not) raise an exception.
//So now you can check results or expected state change.
end;
Option 2
As David has suggested, you can write your own exception handling inside your test. But you'll note that it can get a little messy, and you'll probably prefer option 1 in most cases. Especially when you have the added benefit that distinctly named tests make it easier to identify exactly what went wrong.
procedure MyTests.TestMultipleBadCasesInTheSameTest;
begin
try
DoSomething('Bad1');
//This time, although you're expecting an exception and lines
//here shouldn't be executed:
//**You've taken on the responsibility** of checking that an
//exception is raised. So **if** the next line is called, the
//expected exception **DID NOT HAPPEN**!
Fail('Expected exception for case 1 not raised');
except
//Swallow the expected exception only!
on ESomethingBadHappened do;
//One of the few times doing nothing and simply swallowing an
//exception is the right thing to do.
//NOTE: Any other exception will escape the test and be reported
//as an error by DUnit
end;
try
DoSomething('Bad2');
Fail('Expected exception for case 2 not raised');
except
on E: ESomethingBadHappened do
CheckEquals('ExpectedErrorMessage', E.Message);
//One advantage of the manual checking is that you can check
//specific attributes of the exception object.
//You could also check objects used in the DoSomething method
//e.g. to ensure state is rolled back correctly as a result of
//the error.
end;
end;
NB! NB! Something very important to note in option 2. You need to be careful about what exception class you swallow. DUnit's Fail() method raises an ETestFailure exception to report to the framework that the test failed. And you wouldn't want to accidentally swallow the exception that's going to trigger the test failure for expected exception.
The subtle issues related exception testing make it important to: test first, ensure you have the correct failure, and only then implement the production code change to get a pass. The process will significantly reduce the chances of a dud test.

Avoid handling EAssertionFailed

Generally I make my exception handlers handle only very specific exceptions, so that the code doesn't try to recover from exceptions that weren't anticipated and potentially aren't recoverable.
But there are a few places where it is impossible to know ahead of time what exceptions could legitimately come up from which recovery is perfectly possible (because of calls to libraries that don't specify what exceptions they might raise). So in these special cases I do in fact trap all exceptions: on e:Exception ....
But this also handles EAssertionFailed exceptions, which really should never be handled because they imply incorrect code.
So I've started writing exception handlers like this:
on e:Exception do
begin
if e is EAssertionFailed then
begin
raise;
end;
…
This just seems ugly and error-prone. (What if I forget the if e is EAssertionFailed code?)
Is there a better way? (Should I just never ever use on e:Exception …, except in a top level handler that aborts the program?)
Update
I think David's comment below is correct - never use a blanket exception handler (other than to log the exception and stop the program).
The problem then becomes "How can I get a list of all exception types that are safe to handle arising from some arbitrary bit of code?" I don't think this is one that Stack Overflow can answer.
(The particular problem is a 3rd party library that reads a configuration file. There are fairly obvious exceptions for filing system errors, but the library seems to raise numerous and almost random exception types for syntax errors within the file itself. I don't think I could ever be sure that I've exhausted the possibilities. I certainly cannot have my application fall over because the configuration file couldn't be read!)
The standard Delphi exception handling pattern is
try
// Some code which may raise an exception
except
on E: SomeError do begin
// Handle SomeError and derived classes
end;
on E: SomeOtherError do begin
// Handle SomeOtherError and derived classes
end;
...
end;
it does not require the use of on E:Exception ….
If you don't know the exception type you should not handle it; or sometimes (ex in DLL code) you should handle ALL exceptions.
Why don't to use this:
try
...
except
on EAssertionFailed do raise;
on E: .... do ... ; // your exception
on E: .... do ... ; // your exception
on E: .... do ... ; // your exception
on E: Exception do ... ; // all other exceptions
end;
If any of the handlers in the exception block matches the exception, control passes to the first such handler. An exception handler 'matches' an exception just in case the type in the handler is the class of the exception or an ancestor of that class.
Or you can remove on EAssertionFailed do raise; and on E: Exception do ... ; from the exception block and then exception handler will be searched in the next-most-recently entered try...except statement that has not yet exited.
I'm offering my own answer here to see what the community thinks.
The point wasn't a misunderstanding of how exception handling works in Delphi. I've summarised the particular problem in my update to the question.
My solution is going to be to convert the many different exception types the library can raise into a single type that I can handle explicitly. I'll put a blanket exception handler around the one problem call to the library. This handler will raise a new exception of a known type, to be caught explicitly higher up at the appropriate time.
(And no, the exceptions raised by the library call do not have any common base type other than Exception itself, so I really do have to catch all exceptions if I want to recover from a failure in the library call.)
Crucially, I believe that although I cannot hope to enumerate all the exceptions that this library call could raise, I can be sure that it won't put the application into an unstable state when it raises an exception.
Update
In the end I was able to use something like this:
on e:Exception do
begin
if (e.ClassType = Exception) or (e is EConvertError) or
(e is EVariantError) then
begin
ShowMessage('The application settings could not be loaded.');
end
else
begin
// An exception we didn't anticipate. Assume it is fatal.
raise;
end;
end;
A lot of the recoverable exceptions raised by the library are of type Exception, but note that only Exception and the specified descendants are handled. The others are raised indirectly by the runtime library. There might turn out to be other exceptions that should be handled too, but this seems like a good start. It's better to have the application bail out when it needn't have done so than to continue running and corrupt the user's data.

Change Icon of a raised Exception in Delphi

In our application we have created several Exceptions classes for all our needs. But now the problem is, that the raised Exception Dialog always is MessageType mtError and of course shows the mtError-Icon.
For a few of our Exceptions i would prefer a less agressive Icon/MessageType, for example MessageType mtInformation.
Is there any way to change the Icon directly in the Exceptionclass without raising and catching it again with try...except on each occurence?
You will need to customise the top level exception handler.
Write an event handler and attach it to Application.OnException. You can then handle particular exceptions any way you like.
The event handler will look like this:
procedure TMainForm.ApplicationException(Sender: TObject; E: Exception);
begin
if E is EMyException then
ShowExpectedMessageBox(E)
else
Application.ShowException(E);
end;
Obviously you have to write the code for ShowExpectedMessageBox. This can show a message box using whatever icon you prefer.
The OnException event is called when an unhandled exception reaches the message loop. If you don't have an event handler, the framework calls Application.ShowException. Which is what you should do for those exceptions that do not get your special treatment.

Exceptions on DBGrid editing

I have implemented an editable DBGrid. If a field is not filled correctly an exception is thrown and a message is shown, for example:
'08:00::00' is not a valid time
How can I catch those exceptions so that I can show messages written by me instead of the automatically generated ones? I would be thankful for any help.
As #teran pointed in his comment, the exception is raised by the TDataSet (or one of it's components) that is bind to the TDBGrid, or by the DB engine itself.
You can try to handle the TDataSet.OnPostError (see also OnUpdateError and OnEditError):
TDataSet.OnPostError: Occurs when an application attempts to modify or insert a record and
an exception is raised. Write an OnPostError event handler to handle
exceptions that occur when an attempt to post a record fails.
Note that you could always use Application.OnException global event handler to catch any EDBxxx exceptions in your application.
EDIT: The EConvertError exception is raised before any actual data modifications, or any Post operation by the TDateTimeField field i.e.:
0045af91 +085 Project1.exe SysUtils StrToDateTime <- EConvertError
004ab76a +042 Project1.exe Db TDateTimeField.SetAsString
004a9827 +007 Project1.exe Db TField.SetText
004a95d9 +029 Project1.exe Db TField.SetEditText
004d6448 +014 Project1.exe DBGrids TCustomDBGrid.UpdateData
004d087f +02b Project1.exe DBGrids TGridDataLink.UpdateData
004d599a +01a Project1.exe DBGrids TCustomDBGrid.MoveCol
StrToDateTime is throwing the exception inside TDateTimeField.SetAsString, not touching the data, and
the TDataSet.OnxxxError event handlers will not be fired at all.
So your choices are (test the application in release mode):
1.Intercept and handle EConvertError via Application.OnException.
2.Use TField.EditMask to restrict user input to a valid time format e.g. !90:00;1;_ or use inplace DateTimePicker editor inside your Grid. (and avoid catching this exception).
3.Override TDateTimeField: use persistent fields with your TDataSet and create an inter-poser class as such:
type
TDateTimeField = class(Db.TDateTimeField)
protected
procedure SetAsString(const Value: string); override;
end;
TForm1 = class(TForm)
...
procedure TDateTimeField.SetAsString(const Value: string);
begin
try
inherited SetAsString(Value);
except
on E: EConvertError do
begin
ShowMessage(E.Message);
Abort;
end;
end;
end;
If an exception is being raised then there should be two error messages being shown when run in the debugger. One of these will be caught by the debugger and the 2nd handled by the UI (when running your program as a user you will only see the 2nd).
The exception error message should have contain a string like
Appname.exe raised exception EExceptionName with message XXX
You will need to take note of EExceptionName.
Around the block of code creating the exception you will need to write
...
try
code that can cause the exception here
except
on e: EExceptionName do
begin
ShowMessage('Your apps nicer error message here');
end;
end;
note - if you don't make a call to exit; after handling an exception, your code will continue executing everything after your try..except block. Also, if there are many things that can cause the same error message in the same code block then you may not be able to write anything too specific. e.Message is a string that holds the message that is shown in the unhandled exception and may be useful to also present to the user.
Also try to move away from BDE - ADO is far better supported on modern systems.

Resources