Who is responsible for error checking and handling?
I don't have any of the expensive component libraries such as DevExpress or TMS Components etc so I cannot look at source to get an idea of how most components manage error handling.
Specifically what I am wanting to know is should there be a limit to how many errors and warnings component developers should try to capture? Is there a balance between having meaningful error checking and just making it too easy for developers using your component?
Here is an example using a few scenarios:
Note these are directly from the components source (made up for example purposes)
procedure TMyComponent.AddFromFile(FileName: string);
begin
FBitmap.LoadFromFile(FileName);
end;
or
procedure TMyComponent.AddFromFile(FileName: string);
begin
if FileExists(FileName) then
begin
FBitmap.LoadFromFile(FileName);
end
else
raise Exception.Create(FileName + ' does not exist.');
end;
And these last two are using an instance of the component at runtime:
procedure TForm1.FormCreate(Sender: TObject);
begin
MyComponent1.AddFromFile('D:\Test.bmp');
end;
or
procedure TForm1.FormCreate(Sender: TObject);
begin
if FileExists('D:\Test.bmp') then
begin
MyComponent1.AddFromFile('D:\Test.bmp');
end
else
raise Exception.Create('D:\Test.bmp does not exist.');
end;
I guess it comes down to who should error check and handle what? Is the component developer responsible for handling these types of checking or the user of the component?
As I am writing this I believe both component developer and user should handle such checking but I am unsure, so I am looking for what the general consensus amongst developers is.
Thanks.
To answer your specific queestion:
Specifically what I am wanting to know is should there be a limit to how many errors and warnings component developers should try to capture? Is there a balance between having meaningful error checking and just making it too easy for developers using your component?
The general rule about exception handling is that you should only catch exceptions you know how to handle, and let others propagate to higher code that may know how to handle it. If an exception is raised inside of your component, the component needs to decide whether to:
handle that particular exception internally and gracefully move on to other things without notifying the caller at all.
re-throw the exception (maybe with tweaks made to it), or re-throw a whole new exception, to allow the caller to identify and handle that specific failure, if desired.
ignore the exception (don't catch it at all) and just let it propagate as-is.
If an API used by your component returns an error code instead of raising an exception, the component needs to decide how to handle that as well. Whether to ignore the error and move on, or raise an exception to make it more apparent.
In your particular example, I prefer the following approach:
type
EMyComponentAddError = class(Exception)
private
FFileName: String;
begin
constructor CreateWithFileName(const AFileName: string);
property FileName: string read FFileName;
end;
constructor EMyComponentAddError.CreateWithFileName(const AFileName: string);
begin
inherited CreateFmt('Unable to add file: %s', [AFileName]);
FFileName := AFileName;
end;
procedure TMyComponent.AddFromFile(FileName: string);
begin
try
FBitmap.LoadFromFile(FileName);
except
Exception.RaiseOuterException(EMyComponentAddError.CreateWithFileName(FileName));
end;
end;
This allows your component to recognize that an error occurred, act on it as needed, and still report component-specific information to the caller without losing the original error that caused the actual failure. If the caller is interested in the details, it can catch the exception, look at its InnerException property, access custom properties if present, etc.
For example:
procedure TForm1.FormCreate(Sender: TObject);
begin
MyComponent1.AddFromFile('D:\Test.bmp');
end;
Let's assume MyComponent1.AddFromFile('D:\Test.bmp'); fails. The default exception handler will catch it and display a popup message that says:
Unable to add file: D:\Test.bmp
Useful, but little details, as it could have failed for any number of reasons. Maybe the file could not be opened, but why? Non-existant vs no permission? Maybe the file was opened but corrupted? Maybe memory could not be allocated? And so on.
The caller could catch it and display more useful info, if so desired (it is not required - the component provides the info, the caller decides whether to use it or not):
procedure TForm1.FormCreate(Sender: TObject);
begin
try
MyComponent1.AddFromFile('D:\Test.bmp');
except
on E: EMyComponentAddError do
begin
ShowMessage('There was a problem adding a file:'+sLineBreak+E.FileName+sLineBreak+sLineBreak+E.InnerException.Message);
Sysutils.Abort;
end;
end;
end;
Or:
procedure TForm1.FormCreate(Sender: TObject);
begin
try
MyComponent1.AddFromFile('D:\Test.bmp');
except
on E: EMyComponentAddError do
begin
raise Exception.CreateFmt('There was a problem adding a file:'#10'%s'#10#10'%s', [E.FileName, E.InnerException.Message]);
end;
end;
end;
Either of which would display:
There was a problem adding a file:
D:\Test.bmp
The file was not found
As David said we only need this
procedure TMyComponent.AddFromFile(FileName: string);
begin
FBitmap.LoadFromFile(FileName);
end;
This will check that
there is an existing file
in this file is a valid bitmap
Now it depends on the application, how important is this for the application. If this TForm1 is the Application.MainForm, every exception you did not catch inside the creation process will terminate the application. This is sometimes a valid behavior.
Very important, the application cannot run without
procedure TForm1.Form1Create(Sender:TObject);
begin
MyComponent.AddFromFile( 'D:\Test.bmp' );
end;
or wrap the exception for a user-friendly message
procedure TForm1.Form1Create(Sender:TObject);
begin
try
MyComponent.AddFromFile( 'D:\Test.bmp' );
except
on E: Exception do
raise Exception.Create( 'Sorry, I cannot run, because of: ' + E.Message );
end;
end;
Very important, but we have a fallback to handle this, maybe
procedure TForm1.Form1Create(Sender:TObject);
var
LBitmapFiles : TStringList;
LBitmapIdx : Integer;
LBitmapLoaded : Boolean;
LErrorStore : TStringList;
begin
LBitmapFiles := nil;
LErrorStore := nil;
try
LBitmapFiles := TStringList.Create;
LErrorStore := TStringList.Create;
LBitmapFiles.Add( 'D:\Test.bmp' );
LBitmapFiles.Add( 'D:\Fallback.bmp' );
LBitmapLoaded := False;
while not LBitmapLoaded and ( LBitmapIdx < LBitmapFiles.Count ) do
try
MyComponent.AddFromFile( LBitmapFiles[LBitmapIdx] );
LBitmapLoaded := True;
except
on E: Exception do
begin
LErrorStore.Add( LBitmapFiles[LBitmapIdx] + ': ' + E.Message );
Inc( LBitmapIdx );
end;
end;
if not LBitmapLoaded then
raise Exception.Create( 'Sorry, I cannot run, because of: ' + LErrorStore.Text );
finally
LErrorStore.Free;
LBitmapFiles.Free;
end;
end;
There are other fallbacks possible and this also depends on the application (f.i. set a dummy bitmap to the component) to get the application to work properly.
Not important, if we have no image ... we have no image, who cares
procedure TForm1.Form1Create(Sender:TObject);
const
CBitmapFile = 'D:\Test.bmp';
begin
// check, if there is a file
if FileExists( CBitmapFile ) then
try
MyComponent.AddFromFile( CBitmapFile );
except
on E: Exception do
begin
// Maybe log the exception
SomeLogger.Log( E );
// Maybe set some extra parameters for the application to know, this has failed
RunningWithoutBitmap();
end;
end
else
// Maybe set some extra parameters for the application to know, this has failed
RunningWithoutBitmap();
end;
Component
procedure TMyComponent.AddFromFile(FileName: string);
begin
FBitmap.LoadFromFile(FileName);
end;
This is all you need. If the bitmap object cannot load the file, for whatever reason, it will raise an exception. Let that exception propagate to the consumer of the code.
There's really no point trying to test whether or not the file exists. What if the file exists and it is not a bitmap file? What if the file exists, is a bitmap file, but the disk has a duff sector and the file read fails? If you attempt to check for all error conditions, you will just be repeating the checks that the LoadFromFile method already does.
Some error conditions cannot possibly be checked from the outside. An error that only becomes apparent part way through reading the file cannot reasonably be checked from the outside.
One very common consequence of over-zealous, duplicate error checking is that you end up with code that produces errors in scenarios where there should be none. If you get your error checking wrong you could end up reporting an error that would not have occurred had you let the underlying code run.
Consumer
procedure TForm1.FormCreate(Sender: TObject);
begin
MyComponent1.AddFromFile('D:\Test.bmp');
end;
At this point the decision is more difficult. I would typically expect the following question to be the driver of the decision:
Is it an expected, and reasonable event, for the file not to be present?
If the answer to that question is yes, then you should consider handling the exception in the FormCreate method. Again, testing FileExists() catches just one failure mode, albeit a common one. Perhaps you should use a try/except block to catch the error.
If the answer to the question is no, let the error propagate.
That said, you should also consider whether or not you want an exception to be thrown from your form's OnCreate event handler. That may be perfectly reasonable, but it is certainly conceivable that you will not wish to do this.
Related
Suppose I have the following routine:
function ReadFile(f : TFilename) : Boolean;
var
fs : TFileStream;
begin
Result := False;
try
fs := TFileStream.Create(f, ...);
try
// read file ...
Result := True;
finally
FreeAndNil(fs);
end;
except
// handle exceptions ...
end;
end;
What are the implications of having the except and finally transposed? I have seen plenty of posts with them both ways around, but I haven't seen a clear explanation of which is appropriate in which cases (I still think it is curious that in the above construct, the finally block executes after the except block!).
I have also seen posts that suggest that mixing try..except and try..finally blocks is not a good idea. How can you avoid it in situations where a routine throws an exception as part of normal operation - such as in some of the Indy routines?
There is no single correct way to write this. The two variants do different things. You may prefer one version in one scenario, the other in a different scenario.
Version 1, finally inner-most
function ReadFile(f : TFilename) : Boolean;
var
fs : TFileStream;
begin
Result := False;
try
fs := TFileStream.Create(f, ...);
try
// read file ...
Result := True;
finally
FreeAndNil(fs);
end;
except
// handle exceptions ...
end;
end;
Version 2, finally outer-most
function ReadFile(f : TFilename) : Boolean;
var
fs : TFileStream;
begin
Result := False;
fs := TFileStream.Create(f, ...);
try
try
// read file ...
Result := True;
except
// handle exceptions ...
end;
finally
FreeAndNil(fs);
end;
end;
The big difference is how the code behaves if TFileStream.Create raises an exception, a far from implausible eventuality. In version 1, the exception will be caught and handled inside ReadFile. In version 2, the exception will be passed out of ReadFile and on up the chain of exception handlers.
Asides
You state:
I still think it is curious that in the above construct, the finally block executes after the except block!
That is not true for the code in your question, version 1 above. Perhaps you don't yet fully understand how finally and blocks operate.
A common mistake that is often observed, is a desire to catch and handle exceptions as soon as possible. That's the wrong strategy. The whole point about an exception is that it is not meant to happen and you usually don't know what to do when it does happen. Your goal is to handle exceptions as late as possible. For the vast majority of code you should simply not handle exceptions. Let them float upwards to a point in the code that is able to deal with the error.
I'm using Delphi 7 and in an attempt to handle all the possible exceptions being thrown during the run of the program. I used Application.OnException := HandlerProcedure; to handle exceptions but when exception occurs, HandlerProcedure never gets called. In order to assure if it really works, I raised exception after I assigned Application.OnException as below:
Application.OnException := HandlerProcedure;
raise Exception.Create('Exception');
and defined HandlerProcedure as:
procedure TFormMain.HandlerProcedure(Sender: TObject; E: Exception);
begin
ShowMessage('Exception.');
Exit;
end;
But HandlerProcedure never gets called. How can I make it handle all the exceptions?
If you want to intercept ALL exceptions, you need to implement a RTLUnwindProc low-level procedure.
This is a bit low-level (e.g. it needs asm skills), so you should better rely on existing code. See this stack overflow question. I even put some reference code (including low-level asm, working with Delphi 7 and later under Win32) in my own answer.
Something is wrong in your code. The example from Embarcadero's website is working perfect.
{
In addition to displaying the exception message, which
happens by default, the following code shuts down the
application when an exception is not caught and handled.
AppException should be declared a method of TForm1.
}
procedure TForm1.FormCreate(Sender: TObject);
begin
Application.OnException := AppException;
end;
procedure TForm1.AppException(Sender: TObject; E: Exception);
begin
Application.ShowException(E);
Application.Terminate;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
raise EPasswordInvalid.Create('Incorrect password entered');
end;
Also good practices on handling errors on Delphi are described here.
In order to further investigate the problem you have, you should take a look at this https://stackoverflow.com/questions/1259563/good-os-delphi-exception-handling-libraries
If you are using a third party exception handler such as madExcept, Application.OnException no longer fires. You must instead code TMadExceptionHandler.OnException event or directly call RegisterExceptionHandler.
I have a threaded application and for some purpose I want to pass call stack information of a catched exception to a new custom exception:
try
//here an unknown exception is rissen
except
on E: Exception do
begin
if ... then
raise EMyException.Create(E, CallStackOfExceptionEAsString);
end;
end;
What is the best way to do this, preferably using EurekaLog? I am using Delphi 2006 btw.
EurekaLog exposes several event handlers like OnExceptionNotify.
You can implement these in your code. For example: procedure EurekaLogExceptionNotify(
EurekaExceptionRecord: TEurekaExceptionRecord; var Handled: Boolean);
Here you can see a TEurekaExceptionRecord which is defined in ExceptionLog.pas. But you maybe just own the non-source version which works just fine.
The record has a EurekaExceptionRecord.CallStack list. This proprietary list can be converted to TStringsusing the CallStackToStrings method which is also defined in the ExceptionLog unit.
Here is an example where I write the CallStack into a StringList.
CallStackList := TStringList.Create;
try
CallStackToStrings(EurekaExceptionRecord.CallStack, CallStackList);
LogMessage := 'An unhandled exception occured. Here is the CallStack.' + #13#10
+ CallStackList.Text;
finally
CallStackList.Free;
end;
At least from this starting point you should be able to investigate the exposed functions, records etc.. All information is accessible.
EurekaLog provides a function GetLastExceptionCallStack() (defined in unit ExceptionLog.pas).
Using this I have written the following function (based on example code here):
function GetLastEurekalogCallStackAsString(): string;
{$IFDEF EUREKALOG}
var
Stack: TEurekaStackList;
Str: TStringList;
{$ENDIF}
begin
{$IFDEF EUREKALOG}
Stack := GetLastExceptionCallStack();
try
Str := TStringList.Create;
try
CallStackToStrings(Stack, Str);
Result := Str.Text;
finally
FreeAndNil(Str);
end;
finally
FreeAndNil(Stack);
end;
{$ELSE}
Result := '';
{$ENDIF}
end;
So you can write:
try
//here an unknown exception is rissen
except
on E: Exception do
begin
if ... then
raise EMyException.Create(E, GetLastEurekalogCallStackAsString());
end;
end;
EurekaLog 7 has Chained Exception support, which is specifically designed for this task. Just enable it in options (it is enabled by default) and use:
try
// here an unknown exception is rissen
except
on E: Exception do
begin
if ... then
Exception.RaiseOuterException(EMyException.Create(E.Message));
// for old IDEs:
// raise EMyException.Create(E.Message);
end;
end;
While browsing System.Zip (Delphi XE2) to see how it works, I found this function:
procedure VerifyWrite(Stream: TStream; var Buffer; Count: Integer);
begin
if Stream.Write(Buffer, Count) <> Count then
raise EZipException.CreateRes(#SZipErrorWrite) at ReturnAddress;
end;
It's the at ReturnAddress part that sort of puzzles me.
I didn't know that at was a valid keyword (the syntax highlighter doesn't seem to recognise it either).
According to the IDE it's declared as System.ReturnAddress, but I can only find it declared as a label somewhere in the (asm) code of procedure _HandleAnyException;. The system unit is full of references to it though.
So what I would like to know is this:
What is ReturnAddress?
What exactly does Raise Exception.Create ... at ReturnAddress do?
Bonuspoints if you can give a real-world example of where this would be a useful construct, or if you can advice against using it.
ReturnAddress is the address to which VerifyWrite would have returned when finished.
Raise Exception.Create... at ReturnAddress means that when the exception dialog is displayed, it would indicate the address of the exception as being at ReturnAddress. In other words, the exception message would read Exception <whatever> raised at <ReturnAddress>: <Exception Message>.
Here is an excerpt from the help file for Delphi 7. It's nearly the same as the online version.
To raise an exception object, use an instance of the exception
class with a raise statement. For example,
raise EMathError.Create;
In general, the form of a raise statement is
raise object at address
where object and at address are both optional; see
Re-raising exceptions. When an address is specified,
it can be any expression that evaluates to a pointer
type, but is usually a pointer to a procedure or function.
For example:
raise Exception.Create('Missing parameter') at #MyFunction;
Use this option to raise the exception from an earlier point
in the stack than the one where the error actually occurred.
Note the last sentence in particular. It's pretty specific about the use of at <address>.
ReturnAddr was not a puzzle with previous Delphi versions. Consider next test (Delphi XE):
procedure RaiseTest1;
procedure RaiseException(ReturnAddr: Pointer);
begin
raise Exception.Create('OOPS!') at ReturnAddr;
end;
asm
POP EAX
JMP RaiseException
end;
procedure RaiseTest2;
begin
raise Exception.Create('OOPS!');
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
RaiseTest1;
end;
procedure TForm1.Button4Click(Sender: TObject);
begin
RaiseTest2;
end;
if you press Button3 under debugger and press 'Break' in exception messagebox, debugger stops at
procedure TForm1.Button3Click(Sender: TObject);
begin
RaiseTest1; // <-- here
end;
if you press Button4, debugger stops at
procedure RaiseTest2;
begin
raise Exception.Create('OOPS!'); // <-- here
end;
As you can see RaiseTest1 modifies default exception stack frame and makes debugging a bit more straightforward since the only purpose of RaiseTest1(2) procedures is to raise an exception.
I guess something changed in XE2 so that ReturnAddr syntax is simplified.
How to avoid an error from displaying the little Windows error box?
Try and Except dont work because the error isnt showned by Delphi but from Program or I think from Windows.
try
Size:=TFileStream.Create(BitFile,fmOpenRead);
except on E: EFCreateError
do EC.Add('Error: ' + IntToStr(GetLastError));
end;
Is the error shown in your application? Otherwise put, is it an unhandled exception? Or is it a box displayed by Windows or by an external application?
You say 'event', but event handlers can contain try..except blocks too.
If it is an exception, and you don't know where it's coming from, you can use the TApplicationEvents class to attach the Application.OnException event. It will fire on all unhandled exceptions. There you can catch it, or rather, set a breakpoint and use the stack trace to see where the exception is coming from.
An error box doesn't imply an exception has been raised. An error box can be explicitly shown in code.
So, it seems your question is "How can I prevent 3rd party code from working As Designed?". Beside decompiling the binaries, I'm afraid I can't suggest much, especially if you don't have the source.
If you have the source code and know the routine that needs to be replaced, you could write your own replacement and "hijack" the routine at runtime. This is the method used by, for example, the fastcode project to replace delphi's routine without recompiling the VCL. You can see the implementation in their project.
http://fastcode.sourceforge.net/
Unit: FastcodePatch.pas
Here it is
private
{ Private declarations }
public
procedure MyExceptionHandler(Sender : TObject; E : Exception ); //define exception handler
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.MyExceptionHandler(Sender:TObject;E:Exception);
begin
//Do nothing
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Application.OnException := MyExceptionHandler;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
//Generate an exception
asm
mov eax,8272
mov [eax],$2FFFFF
end
end;