PL/SQL Exception handling in procedures - stored-procedures

This is kind of a question about best practices. I have a PL/SQL block similar to this
DECLARE
--work variables
PROCEDURE p1(in_parameter1, out_parameter1, out_parameter2...) IS
BEGIN
--do stuff
--select ( ... ) into ( ... ) from t1 where ( ... )
END;
PROCEDURE p2(in_parameter1, out_parameter1, out_parameter2...) IS
BEGIN
--do stuff
--insert/update tables
--do more stuff
END;
BEGIN -- MAIN PROCESS STARTS HERE
open c1;
fetch c1 into c1RowData;
EXIT WHEN c1%NOTFOUND
--call procedure1
--do stuff
--call procedure2
--do stuff
--do stuff
--call procedure1
--call procedure2
END;
/
EXIT;
The statements in procedures p1 and p2 could possibly raise exceptions ( NO_DATA_FOUND, DUP_VAL_ON_INDEX, ... ).
What do you think is the best way to handle this exceptions? Should they be handled inside the procedures or do you think that I should surround every call the procedures in the main body with a TRY-CATCH block?

Personally I'd catch them inside the procedures they're thrown from. This means that you have much more control with regards to how they're handled externally. For example, you can throw them again as user defined exceptions which you can embellish with more information about exactly what went wrong.
'Failed to find a matching row in table a for value b'
is much more descriptive outside the procedure than
'no data found'
But this is realy dependent on:
The error reporting requirements of the calling application
The actual functionality implemented where you 'do stuff' within the procedures
For example, say you wanted to run procedure 2 even though the select in procedure 1 found no rows.. You'd need to catch the exception in procedure 1 and ignore it. If you didn't, then it'd be thrown to the exception handler in procedure 2.
Or, say you wanted procedure 1 to insert a row in the case where the select didn't find anything, in this case you'd need to catch the exception and perform the insert in the exception handler.
Now, before anyone jumps on me, I'm not recommending you use exception handlers for contrlling the flow of execution within your code, these examples are the theoretical, but hopefully you get the idea..

You should try to handle exceptions at the source (i.e. within the procedure that raises them). This gives you greater scope for reporting where the issue occurred and, usually, a greater chance of being able to gracefully rectify the issue without passing a nasty Oracle error message up the stack to the user.
Of course you can both handle the error and re-raise it if you really needed to but as StevieG answered you can also raise user defined exceptions which are generally more application specific and more helpful to your users/other PL/SQL code.
There is an ASKTOM discussion on custom error handling here:
http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:4684561825338

It is best when the exceptions handled inside the procedure. It will be resulting quick. Not only that if we reuse the same procedure in some other functions, there also we don't need to handle exceptions. I my point of view, exceptions can handle inside the procedure itself is the best one.

Related

ADODataSet.Open bypasses try catch with `ArgumentOutOfRange` exception, hangs application - Delphi 10.2

I maintain an application that runs as a service in a server environment. It is multithreaded, where each thread does work according to a task queue. This task queue is just a list of strings with "job types" as their values. So while several threads may be running, each thread would be a different job, and each thread internally runs just one task at a time.
I'm experiencing an intermittent issue that happens when calling Open on a TADODataSet. Sometimes, not always, and with no discernible pattern, Data.Win.ADODB will throw an EArgumentOutOfRangeException, bypassing my own attempt to catch any exceptions. This exception hangs the entire thread and prevents future execution from being possible until I completely restart the service.
Being relatively new to the world of Delphi, I've been scratching my head at this issue for quite some time, and have struggled to find any answers. My question is: why is this happening, and how do I stop, catch, or fix it?
Here is a snippet of my offending code. This is the method from which the stack trace originates. It is called from another method in the same unit, where I open a different dataset, loop through its records, and on each record call this function to get some info based on the value passed in.
function TFQFoo.DoSomething(IncNo : Int64): string;
var
ItemList : string;
MySQL: string;
ComponentString: string;
begin
result:='';
if IncNo<=0 then
Exit;
ItemList := '';
MyQuery.Close;
MySQL := 'select ID from tbl ' +
' where val = ' + IntToStr(IncNo) +
' order by col1 DESC, col2, col3';
try
try
MyQuery.CommandText := (MySQL);
MyQuery.Open;
while not (MyQuery.EOF) do
begin
if (ItemList <> '') then
ItemList := ItemList + ',';
ItemList := ItemList +
MyQuery.FieldbyName('ID').asstring;
MyQuery.Next;
end;
except
// exception handling code omitted for brevity -- none of it
// is ever reached, anyway (see below)
end;
finally
MyQuery.Close;
end;
Result := ItemList;
end;
The call stack from the exception indicates that it's occurring at Open. No try..catch block will capture the exception and log it for me -- I have to use EurekaLog in order to see any details. The stack trace methods (too big to post here) look like:
TFQFoo.DoSomething
TDataSet.Open
... internal things
TADOConnection.ExecuteComplete
CheckForAsyncExecute
...
TCustomConnection.GetDataSet
TListHelper.GetItemRange
Thinking possibly my TADODataSet component was somehow getting corrupted / its properties altered at runtime, I added some logging to capture that data for me so I could see if something funky was going on there. I didn't see anything, but here it is in case it's pertinent.
object MyQuery: TADODataSet
AutoCalcFields = False
CacheSize = 15
Connection = FGlobals.RIMSDB
CursorType = ctStatic
LockType = ltReadOnly
CommandText =
'select ID from tbl where val = 202005070074 order by col1 ' +
'DESC, col2, col3'
ParamCheck = False
Parameters = <>
Left = 32
Top = 216
end
For the curious, this is the method actually throwing the exception, from Data.Win.ADODB. Note the except, which I guess hops over my own try..catch block and sends the exception straight to EurekaLog.
procedure CheckForAsyncExecute;
var
I: Integer;
begin
try
if not Assigned(pError) and Assigned(pRecordset) and
((pRecordset.State and adStateOpen) <> 0) then
for I := 0 to DataSetCount - 1 do
if (DataSets[I].Recordset = pRecordset) and (eoAsyncExecute in DataSets[I].ExecuteOptions) then
begin
DataSets[I].OpenCursorComplete;
Break;
end;
except
ApplicationHandleException(Self);
end;
end;
What I have tried:
Many, many iterations of tweaking component properties on the ADODataSet itself
Using the CommandText and Parameters from within the designer, and assigning the parameter prior to execution at runtime
Adding / removing a (NOLOCK) hint to the query itself
Taken the problem to senior members of my team for input
Googling (for hours)
Reading up on Delphi and ADO documentation (not fruitful for this)
Attempted reproduction - I've never been able to get this to occur on any test system I use. This leads me to think it may be environment-related, but I have absolutely no clue how
My question, restated:
How do I stop this from happening? What am I doing wrong? I don't want to just catch the EArgumentOutOfRangeException; I want to learn why it's happening in the first place and prevent it from happening in the future.
I know that sometimes, the query execution will not return results, but the typical CommandText does not return a result set message is never seen nor encountered due to the lower-level code bypassing my own catch statement. Beyond this, I don't know what else to look for.
I've only found one other occurrence of something similar so far, but it relates just to the exception not getting caught: http://www.delphigroups.info/2/d9/410191.html
The call to ApplicationHandleException(Self) in CheckForAsyncExecute() is swallowing exceptions, which is why your except block is not being triggered:
// in Data.Win.ADODB.pas:
procedure CheckForAsyncExecute;
var
I: Integer;
begin
try
...
except
ApplicationHandleException(Self); // <-- a caught exception is NOT re-raised here!
end;
end;
Inside of the Data.Win.ADODB unit, caught exceptions will call the unit's own ApplicationHandleException() function, which then calls System.Classes.ApplicationHandleException if assigned, otherwise it simply exits:
// in Data.Win.ADODB.pas:
procedure ApplicationHandleException(Sender: TObject);
begin
if Assigned(System.Classes.ApplicationHandleException) then
System.Classes.ApplicationHandleException(Sender);
end;
System.Classes.ApplicationHandleException is initialized to nil.
In both a VCL1 and FMX app, the TApplication constructor assigns the TApplication.HandleException() method to System.Classes.ApplicationHandleException, where HandleException() ignores EAbort exceptions, and calls the TApplication.OnException event handler (if assigned), the TApplication.ShowException() method, or the System.SyUtils.ShowException() function, depending on the type of exception being handled.
1: in a VCL TService app, TServiceApplication uses Vcl.Forms.TApplication internally.
TApplication.ShowException() displays the details of the exception to the user in a popup MessageBox and then exits, and System.SysUtils.ShowException() displays the details of the exception to the user in a Console or MessageBox and then exits.
So, at no point does ADO's CheckForAsyncExecute() re-raise a caught exception into user code. And needless to say, displaying a popup MessageBox in a service is not a good idea, as the user will likely not see it so they can dismiss it.
Of course, the best option would be to avoid the EArgumentOutOfRangeException exception from being raised in the first place. But there are other conditions that can also raise exceptions, too.
So, your only option to handle swallowed ADO exceptions yourself, and avoid popup MessageBoxes, is to assign a TApplication.OnException event handler (either directly, or via the TApplicationEvents component).

Raising exceptions is not showing messages

I am struggling with something I have always perceived as something straightforward, but apparently I am missing something in this case.
I am using Delphi Seattle.
In part of my program I have a certain main calculation (a loop) that repeatedly calls a sub-calculation. In certain cases the sub-calculation result will go towards infinity and cause a floating point overflow exception. I cannot predict this, nor can I define / trap an acceptable maximum value (depends on case) so I need to trap the overflow exception, notify the user and abort the calculation.
I have simplified this calculation to the following example program. The code itself is nonsense of course but the point is to force an overflow exception in a similar program structure as my real application.
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
function MainCalculation: boolean;
function SubCalculation(AFloatingPoint: Double): Double;
public
{ Public declarations }
end;
.........
procedure TForm1.Button1Click(Sender: TObject);
begin
if MainCalculation then
ShowMessage('Calculation succeeded.')
else
ShowMessage('Calculation failed.');
end;
function TForm1.MainCalculation: boolean;
var
ii: Integer;
dd: Double;
begin
try
dd := 1E200;
for ii := 1 to 100 do
dd := dd * SubCalculation(dd);
except
raise Exception.Create('Error in main calculation.');
end;
end;
function TForm1.SubCalculation(AFloatingPoint: Double): Double;
begin
try
result := Power(AFloatingPoint, AFloatingPoint);
except
raise Exception.Create('Error in sub-calculation.');
end;
end;
Running from the debugger, I get three Debugger Exception Notifications:
Exception class $C0000091 (floating point overflow)
Exception in sub-calculation
Exception in main calculation
However, only the last one is shown to the user as an exception.
In my real application it's even worse. When I run the application and the overflow exception occurs, nothing is shown to the user and the (main) calculation just aborts with partial results.
Can anybody explain to me why I don't get the two exceptions raised (as I would expect)? Is there a specific setting somewhere that causes the overflow exception to be treated different? Any suggestion to help me forward will be highly appreciated. Of course, I'll be more than happy to provide additional details, should this be required.
Thanks in advance!
Mark
I don't think there' a misunderstanding on the concept of what an exception is. It's merely that raising a standard Exception (i. e. not an EMyCustomException) with a specific message is not shown to the end-user once it's "nested".
You do have a misunderstanding of some aspects of exceptions. I suspect you correctly understand how they affect code-flow (an area most people struggle with). But any expectation that they will show messages indicates a huge misunderstanding.
Note that it would be fundamentally incorrect for exceptions to in any way automatically display the exception message because if the app runs on an unattended server, the last thing you want is dialogs popping up on screen waiting for a non-existent user to close them.
Even on a front-end, it would be a horrible user experience to display a long chain of exception messages for each and every negative consequence side-effect you choose to report in a boiler-plate (DRY violating) try...except block.
The only time any exception message will be displayed is when code explicitly calls something to do so
E.g.: ShowMessage(SomeException.Message);
or Application.ShowException(SomeException.Message);
You also asked:
but if "raise Exception.Create..." is not meant to display anything, then why does the outer level?
I can see why you might think that; but this is exactly the misunderstanding referred to earlier...
The outer level raise Exception.Create('Some Message'); does NOT display any message. The Delphi framework has its own try...except block which catches the outer level exception, and by default will show the message and class of the exception. You could override the default handler to display a different message or use a different dialog, or not display anything, and simply log the message.
As an exercise, you may want to step through vcl/rtl code using a debugger to see this in action. The source code that ships with Delphi may seem intimidating, but studying that code is an excellent way to learn.
Suggested further reading
RAD Studio Exceptions (link goes directly to Nested Exceptions; but read the whole page)
TApplication.OnException (a similar feature exists for FMX)
TCustomApplicationEvents.OnException
Obviously and understandably you have a concern about tracking information at each exception handler. This is good; but the way you're trying to go about it is flawed. I suggest you read up on the following poss-dups:
How should I re-raise a Delphi exception after logging it?
What is the correct way to nest exceptions? - Using Delphi
I also recommend you consider using an exception handling framework. I'm not sure of the current state of the following, but they're all worth investigating:
Mad Except
Exceptional Magic
Eureka
Jcl Debug
Note that tools like those above are able to generate a call-stack which provides a full chain of calls leading to the trigger exception. Combine this with general trace logging and you have a powerful set of tools to fully investigate most typical errors.
In general you don't want to be writing a large number of try...except blocks. They really should be the (ahem) "exception to the rule".
Side note: Acknowledging your code is merely a sample...
I must point out that as it stands, your question code is seriously flawed. You fail to guarantee the Result of MainCalculation will be initialised no matter if/where an exception might occur and whether or not it's swallowed. You need to be very careful of this to avoid returning the wrong value to callers such as ButtonClick.
The last thing you want is callers incorrectly assuming a failed calculation succeeded or vice-versa. (This is a benefit of the structured exception model and not writing lots of exception handlers.)
Use sysUtils.abort("silent exception")

Delphi -make a function available to other forms beside mainform

I use a function to log application errors to a file.I have it on my main form.How do I tell my other forms, in case of an error,to use this function? Just by chance I added this to my query on some other form:
......
try
ClientDataSet1.Execute;
ClientDataSet1.Close;
except
on E:Exception do begin
ShowMessage('Error : ' + E.Message);
LogError(E);
....
My LogError(E); is not recognized (gives error). Tried adding the function :procedure LogError(E:Exception); to public uses but wont work.
Structured Exception Handling
I'd like to point out that your approach to exception handling is very tedious and wasteful coding. Not to mention quite dangerous. You don't want to be littering your code with individual handlers to log your exceptions when there's a much more practical approach.
Fortunately Delphi provides the means to make this much easier. First a little background about structured exception handling... Most developers make the mistake of trying to deal with errors by writing code like the following:
try
//Do Something
except
//Show Error
//Log Error
end;
The problem with the above code is that it swallows the error. Even though the user sees an error message, the error itself is hidden from the rest of the program. This can result in other parts of the program doing things they should not do because of the error.
Imagine a program that calls the following routines: LoadAccount; ApplyDiscount; ProcessPayment;. But the the first routine raises an error. Which a programmer (mistakenly thinking they're being diligent) decided to "handle" as above. The problem is that the next two routines will still be called as if nothing is wrong! This could mean that a discount is applied to and payment processed for the wrong account!
The point is that structured exception handling saves us from these headaches (provided we don't break the way it works). If the LoadAccount routine didn't try "handle" the exception, then while the application is in an "exception state" the code would simply keep jumping out of routines until it finds a finally / except handler.
This suits an event driven program very nicely.
Suppose a user clicks a button that will cause your program to start a multi-step task: with loops, calls to child methods, creating objects etc. If anything goes wrong, you want to abandon the entire task and wait for the next input. You don't want to stubbornly keep trying to complete the task; because you'll either just get more errors, or worse: later changes will do the wrong thing because earlier required steps did not complete successfully.
Easy Logging of Errors
As mentioned earlier, if you simply leave an exception alone, the error will "bubble up" to an outer exception handler. And in a standard GUI application, that will be the default Application exception handler.
In fact at this stage, doing nothing special: if an exception is raised in the middle of the button click task described earlier, the default exception handler will show an error message to the user. The only thing missing is the logging. Fortunately, Delphi makes it easy for you to intercept the default Application exception handler. So you can provide your own implementation, and log the error as you desire.
All you need to do is:
Write a method using the TExceptionEvent signature.
E.g. procedure MyHandleException(ASender: TObject; E: Exception);.
Then assign your custom handler using: Application.OnException := MyHandleException;
General Logging
Of course, this only covers logging of exceptions. If you want to do any other ad-hoc logging, you want to be able to reuse the code. This basically requires you to move your logging code into a separate unit that all your other units can call as needed.
So putting these things together you might do something as follows:
TLogger = class
public
procedure WriteToLog(S: string); //Performs actual logging of given string
procedure HandleException(ASender: TObject; E: Exception); //Calls WriteToLog
end;
Then you might set it up in your program unit as follows:
begin
Logger := TLogger.Create(...);
Application.OnException := Logger.HandleException;
Logger.WriteToLog('Custom exception handler has been assigned.');
//Rest of application startup code
end;
Final Thoughts
I mentioned that if an unexpected exception occurs, you want to abandon the current task. But what happens if you've already done some things that should now be undone as a result of the error. Then you would simply implement it as follows:
try
//Do something
except
//Undo
raise; //NOTE: more often than not, one should re-raise an
//exception so that callers are aware of the error.
end;
Note that this is the primary use for exception handlers. Yes they can also be used to swallow exceptions. But they should only swallow an exception if it has been truly resolved.
While Delphi is object-oriented language, it can deal with non-OO entities as well.
Think, your main form is property of which form ?
The answer is that main form is not the property of ANY other form, it is a global object. Same exactly thing should be done about your function.
unit Main;
interface uses .......;
type TMainForm = class (TForm)....
....
end;
Procedure LogError(const message: string);
implementation uses .....;
Procedure LogError(const message: string);
begin
...
end;
(**********)
procedure TMainForm.FormCreate(...);
begin
....
end;
...
end.
Now even better option would be to totally decouple LogError from ANY form at all.
Unit MyLogger;
interface uses ....; // but does not uses Main or any other form
procedure SetupLog(....);
// to initialize it and change any settings you may wish
// maybe you would choose to save messages to file
// or use Windows debug interface - OutputDebugString
// or to a network SysLog server
// or just WriteLn it to a text window
// or to some TMemo in a graphic window
// or to file AND to a memo - that also might make sense.
// just keep it extensible
Procedure LogWarning(const message: string);
Procedure LogError(const message: string);
implementation uses ...;
function GenericLoggingSink(.....);
begin
...
end;
Procedure LogError(const message: string);
begin
GenericLoggingSink( Log_Message_Type_Error, message, ... );
end;
Procedure LogWarning(const message: string);
begin
GenericLoggingSink( Log_Message_Type_Warning, message, ... );
end;
And your Main form should just use this unit and this function on the same terms as all other forms in your application.
As part three I suggest you think what you want from your logging procedures.
Actually doing this only makes sense for very simplistic logging needs. Very simplistic.
Otherwise just take any existing Delphi logging frameworks and utilize a lot of functionality implemented there. For example can you just log an object of any class you would write?
The only reason to make your own logging library is "my program is so simplistic, that it does need only the most primitive logging. It would be faster to improvise my own system, than to copy-paste library initialization and setup from examples to a ready-made libraries".
Log4Delphi and Log4D are well-known FLOSS libraries, though they look somewhat abandoned. Maybe there just is nothing left to be extended. Those are most old ones, but there also are some newer FLOSS libraries, an easy example being http://blog.synopse.info/post/2013/02/03/Log-to-the-console
If anything, you can read their texts and learn from them.
There are also commercial libraries like those listed at Is there a way to do normal logging with EureakLog? and Delphi itself is shipped with a limited version of CodeSite logging framework.
Delphi IDE enhancements like CnWizards and GExperts also do come with a simplified debug-oriented logging interface.
Google would bring you even more options if you'd dare :-)

Creating or forcing an error in Delphi

For some of my procedures and functions, I have implemented various checks on parameters in order to force execution to stop if parameters are out of range in one way or another.
I find it better to check for this in my own code rather than have an abnormal crash due to a perhaps bad memory-write.
Consider the simple code:
PROCEDURE Test(OneDigitNumbers:BYTE);
BEGIN
IF OneDigitNumbers>9 THEN ProduceErrorMessage;
END;
begin
Test( 1);
Test( 2);
Test( 9);
Test(12);
end.
I have no problem in actually producing an error message, my only "problem" is that the debugger in Delphi always point to the procedure creating the exception.
Is there a method of creating this exception or error message so that the debugger point to the line where the parameter is out of range?
In my example, it should point to :
Test(12);
and maybe say something like "Parameter out of range. Valid range is 0-9. Parameter passed was: 12"
Even an answer to say that this is NOT possible will be useful (if you know for sure that this is not possible), because then I will just forget about this and make an alternative method for debugging.
To answer the question as asked, you can make the test function inline:
procedure Test(OneDigitNumbers: byte); inline;
The compiler will then write the code for Test into each calling function. Whilst you can do this, my advice is that you do not. It's just a trick but I don't think it really helps you.
If you want to raise the exception at the return address, you can do this:
raise Exception.CreateFmt(
'Exception blah blah at %p.',
[ReturnAddress]
) at ReturnAddress;
If you want to go further up the stack, then you'll have to use something like CaptureStackBackTrace. Combine the back trace with raise at and you can raise the exception at any point in the call stack, if really you think that's a good idea. I do not think it's a good idea, as I explain below.
If you use a good debugging tool, like madExcept, then the call stacks in the madExcept bug reports will tell you all you need to know when an error occurs.
With the extra clarification in the comments, it seems that what you really want to happen is for the exception to contain information from higher up the call stack. To my mind it is a violation of encapsulation to ask the callee to report information about its caller. So if you want to include information from the caller, let the caller catch the exception, add the information, and re-raise.
You're looking for a subrange type.
type
TOneDigitNumber = 0..9;
procedure Test(OneDigitNumbers: TOneDigitNumber);
begin
// Do something
end;
begin
Test( 1);
Test( 2);
Test( 9);
Test(12); // compiler error '[DCC Error] MyStuffTest.pas(33): E1012 Constant expression violates subrange bounds
end.
A bit of an elaboration on my comment to your question
type
EMyOwnRangeError = class(ERangeError)
// You can also add your own member variables for easier inspection
public
constructor CreateFrom(const aRangeError: ERangeError);
end;
constructor EMyOwnRangeError.CreateFrom(const aRangeError: ERangeError);
begin
// Do whatever you need to inspect the call stack in aRangeError
// and modify the message and/or set any extra member variable that you
// you define on EMyOwnRangeError.
// No help from me on this, quite simply because I don't have Delphi
// installed on the machine I am currently working at.
end;
procedure MySpecialTest(const aWhatever: Byte);
begin
try
if (aWhatever < 0) or (aWhatever > SOMEUPPERRANGE) then
raise ERangeError.Create;
// Normal code for MySpecialTest
except
on E: ERangeError do raise EMyOwnRangeError.CreateFrom(E);
else
raise; // Make sure other exceptions are propagated.
end;
end;
I have now tested basically the approach in the idea I got from David Heffernan.
I simply added this simple code in one of my units of reuseables:
PROCEDURE TestError(Par:BYTE);
BEGIN
TRY
FINALLY
IF Par>9 THEN Raise Exception.CreateFmt('Error Blah blah blah at ',[Par]) AT #Par;
END;
END;
When this procedure is called with a parameter higher than 9 then it forces an exception.
Delphi ask "Break or Continue" and I click Break.
The result is almost what I would like, but it is so close that I can live with that.
The debugger pops up with a nice red line on the line AFTER the one calling the procedure.
I tried withouth the TRY-Finally-End also, and then it is plain wrong, actually showing the red line another level back from the callstack..
Anyway. I feel that this result is an awful lot better than what I had before. Now my debugging will be a joy rather than a pain.
Thank you :)

It is possible to resume execution from the point where an exception was raised?

There is an example which illustrates my question:
procedure Test;
begin
try
ShowMessage('1');
raise EWarning.Create(12345, 'Warning: something is happens!');
ShowMessage('2');
except
on E: EWarning do
if IWantToContinue(E.ErrorCode) then
E.SkipThisWarning // shows '1' then '2'
else
E.StopExecution; // shows '1'
end;
end;
function IWantToContinue(const ErrorCode: Integer): Boolean;
begin
//...
end;
I tried to use something like this:
asm
jmp ExceptAddr
end;
but it's wont' work...
Any ideas?
Thanks.
No, it is not possible:
There are two kinds of exceptions: logic exceptions that are raised by the programmer using the raise command, and external exceptions that are initiated by the CPU for various conditions: division by zero, stack overflow, access violation. For the first kind, the logic exceptions, there's nothing you can do because they're part of the application "flow". You can't mess with the flow of 3rd party code, you can't even mess with the flow of your own code.
External exceptions
Those are normally raised as a consequence of running a single CPU instruction, when that instruction fails. In Delphi those are made available as EExternal descendants. The list includes access violations, division by zero, stack overflow, privileged instruction and not-so-many others. Theoretically, for some of those exceptions, the causing condition of the exception could be removed and the single CPU instruction retried, allowing the original code to continue as if no error happened. For example SOME access violations might be "fixed" by actually mapping a page of RAM at the address where the error occurred.
There are provisions in the SEH (Structured Exception Handling) mechanism provided by Windows for dealing with such retry-able errors, and Delphi is using SEH under the hood. Unfortunately Delphi doesn't expose the required elements to make this easily accessible, so using them would be very difficult if not impossible. None the less, for particular types of EExternal errors, smart Delphinians might attempt writing custom SEH code and get things working. If that's the case, please ask a new question mentioning the particular type of error you're getting plus the steps you'd like to take to remove the error condition: you'll either get some working code or a customized explanation of why your idea would not work.
Logic exceptions initiated through the use of raise
Most exceptions fall into this category, because most code will check it's inputs before doing potentially dangerous low level stuff. For example, when trying to access an invalid index in a TList, the index would be checked and an invalid index exception raised before attempting to access the requested index. Without the check, accessing the invalid index would either return invalid data or raise an Access Violation. Both of those conditions would be very hard to track errors, so the invalid index exception is a very good thing. For the sake of this question, even if code were allowed to access an invalid index, causing an Access Violation, it would be impossible to "fix" the code and continue, because there's no way to guess what the correct index should be.
In other words, fixing "logic" exceptions doesn't work, shouldn't work, and it's insanely dangerous. If the code that raises the error is yours then you can simply restructure it to NOT raise exceptions for warnings. If that's not your code, then continuing the exception falls into the "insanely dangerous" category (not to mention it's technically not possible). When looking at already written code, ask yourself: would the code behave properly if the raise Exeption were replaced with ShowMessage? The answer should mostly be "NO, the code would fail anyway". For the very rare, very wrong case of 3rd party code that raises an exception for no good reason, you may ask for specific help on patching the code at run-time to NEVER raise the exception.
Here's what could be in some 3rd party code:
function ThirdPartyCode(A, B: Integer): Integer;
begin
if B = 0 then raise Exception.Create('Division by zero is not possible, you called ThirdPartyCode with B=0!');
Result := A div B;
end;
It should be obvious that continuing that code after the exception is not going to allow stuff to "self heal".
Third party code might also look like this:
procedure DoSomeStuff;
begin
if SomeCondition then
begin
// do useful stuff
end
else
raise Exception.Create('Ooops.');
end;
Where would that code "continue"? Quite obviously not the "do usefull stuff" part, unless the code is specifically designed that way.
Those were, of course, simple examples only scratching the surface. From a technical perspective, "continuing" after an exception as you're suggesting is a lot more difficult then jumping to the address of the error. Method calls use stack space to set up local variables. That space was released in the process of "rolling back" after the error, on the way to your exception handler. finally blocks were executed, possibly de-allocating resources where needed. Jumping back to the originating address would be very wrong, because the calling code no longer has what it expects on stack, it's local variables are no longer what they're ment to be.
If it's your code that's raising the exception
Your code can easily be fixed. Use something like this:
procedure Warning(const ErrorText:string);
begin
if not UserWantsToContinue(ErrorText) then
raise Exception.Create(ErrorText);
end;
// in your raising code, replace:
raise Exception.Create('Some Text');
// with:
Warning('Some Text');
AFAIK no. You have to restructure your code to something like
procedure Test;
begin
ShowMessage('1');
try
raise EWarning.Create(12345, 'Warning: something is happens!');
except
on E: EWarning do
if IWantToContinue(E.ErrorCode) then
// shows '1' then '2'
else
raise; // shows '1'
end;
ShowMessage('2');
end;
In C++, it is possible using an SEH __try/__except block whose __except expression evaluates to EXCEPTION_CONTINUE_EXECUTION.
In Delphi, it is not possible to use SEH directly, AFAIK.
BASICally, your code wont work because ExceptAddr is a function, not a variable. So, your code snippet changes as follows:
{$O-}
procedure TForm1.FormCreate(Sender: TObject);
begin
try
OutputDebugString('entering');
raise Exception.Create('Error Message');
OutputDebugString('leaving');
except on E: Exception do
begin
OutputDebugString(PChar(Format('ExceptAddr = %p', [ExceptAddr])));
asm
CALL ExceptAddr
JMP EAX
end;
end;
end;
end;
..you could nest multiple try-except and determine if continue inside each exception:
try
try
some code
except
ret := message('want to continue?');
if ret <> mrYes then
exit;
end;
some other code to perform when no exception or user choose to continue
except
etc..
end;

Resources