What is an exception?
An exception is simply a signal that something has gone wrong. Under object pascal all exceptions are represented by an exception object, which contains information about the error that has occurred. The most basic exception object simply contains a text string called message, which contains a string representation of the error that occurred.
The exception object is an object like everything else. It is normal to create your own exception objects if your error needs more information than just a string.
Handling errors in Smart Mobile Studio
Whilst we all want to spend our time writing functional code, errors will and do occur in in code from time to time.
In serious code you should handle error situations so that at the very least, the user is informed about the error in your chosen way.
SmartMS uses the event handling approach to error handling. Errors are (mostly) treated as exceptions, which cause program operation to suspend and jump to the nearest exception handler. If you don't have one, this will be the SmartMS default handler - it will report the error and terminate your program.
Often, you will want to handle the error, and continue with your program. For example, you may be trying to display a picture on a page, but cannot find it. So you might display a placeholder instead.
NOTES:
Except: Starts the error trapping clause of a Try statement.
Finally: Starts the unconditional code section of a Try statement.
On: Defines exception handling in a Try Except clause.
Try: Starts code that has error trapping.
TRY, EXCEPT where there are problems
SmartMS provides a simply construct for wrapping code with exception handling. When an exception occurs in the wrapped code (or anything it calls), the code will jump to the exception handling part of the wrapping code:
begin
Try
// The code we want to execute
Except
// This code gets executed if an exception occurs in the above block
end;
end;
|
The Except keyword is used to mark the start of a block of statements that handle an exception in a Try clause. If the Except block can handle the exception, then the program is not terminated.
We literally try to execute some code, which will run except when an error (exception) occurs. Then the except code will take over.
EXCEPT using Delphi:
· | Version 1
In this version, if the Try clause generates an exception the Except clause is executed. This is used to take alternative action when something unexpected goes wrong. The except clause cannot determine the error type however. When the division fails, the code jumps to the except block. The first ShowMessage statement therefore does not get executed. |
· | Version 2
This is similar to version 1, but specifies different actions for different exception types, such as EInOutError. An Else clause can be used as a catch all for unexpected exception types. The general exception type Exception can be used to catch all exception types.
By assigning a Name to the exception, the message text of the exception (Name.Message) can be obtained for display or other uses.
When an exception is raised in a version 2 setup, if the exception is not acted upon by On or Else statements, then a check is made to see if we are in a nested Try block. If so, the Except clause of this parent Try is processed. If no On or Else clause is found, the program terminates.
The Else clause is not really necessary - it is better to use On E:Exception Do, the generic exception handling, since it still provides the error message (E.Message). |
Catching exceptions with Smart Mobile Studio
Catching an exception is done inside a try / except block. If the code inside the block raises an exception then you can safely handle it before either exiting your method or continuing execution.
EXCEPT with Smart Mobile Studio
Please include the units SmartCL.System and System.Complex to use Exceptions. Differently from Delphi, SmartMS does not trap the error automatically. You have, firstly, check if a variable have unexpected type and then start the error trapping in the except block, which you can handle the exception. Here is how you raise an exception:
if (something <> what_you_expect) then
raise exception.create('I expected something else');
|
Example code : Zero divide with a plain Except block in SmartMS
procedure TForm1.btnClearClick(Sender: TObject);
var
number, zero : Integer;
begin
// Try to divide an integer by zero - to raise an exception
Try
zero := 0;
number := 1 div zero;
if IsInfinite(number) then raise Exception.Create('Division by zero') else
ShowMessage('number / zero = '+FloatToStr(number));
Except
ShowMessage('Unknown error encountered');
end;
end;
|
Result is: Unknown error encountered
function btnClearClick(Self, Sender$4) {
var number = 0;
var zero = 0;
try {
zero = 0;
number = $Div(1,zero);
if (IsInfinite(number)) {
throw Exception.Create($New(Exception),"Division by zero");
} else {
alert("number / zero = "+((number)).toString());
}
} catch ($e) {
alert("Unknown error encountered") }
}
|
Example code : Example code : Divide by zero with an Except On clause
procedure TForm1.W3Button1Click(Sender: TObject);
var
number, zero : Integer;
begin
// Try to divide an integer by zero - to raise an exception
Try
zero := 0;
number := 1 div zero;
if IsInfinite(number) then raise Exception.Create('Division by zero') else
ShowMessage('number / zero = '+IntToStr(number));
Except
on E : Exception do
Begin
ShowMessage(E.ClassName+' error raised, with message : '+E.Message);
end;
end;
end;
|
Result is: It will show a message Exception error raised with message : Division by zero
Important : you can determine the type of error that occured by using the generic exception handling - On E:Exception Do. E is a pointer to the exception object that is created by the exception condition. E.ClassName gives the exception type, such as 'EDivByZero', as shown in the final example code.
function W3Button1Click(Self, Sender$6) {
var number$1 = 0;
var zero$1 = 0;
try {
zero$1 = 0;
number$1 = $Div(1,zero$1);
if (IsInfinite(number$1)) {
throw Exception.Create($New(Exception),"Division by zero");
} else {
alert("number / zero = "+number$1.toString());
}
} catch ($e) {
var E = $W($e);
alert(TObject.ClassName($Check(E,"").ClassType)+" error raised, with message : "+
$Check(E,"").FMessage) }
}
|
Being more selective
While catching all types of exception with the same error handler is very effective, there are times when you want to handle specific errors differently. Object Pascal allows you to handle that in the following way:
Example code : Custom Exception
EAbort = class(Exception);
{ Raise abort exception }
procedure DoSomething;
begin
raise EAbort.Create('Abort without dialog');
end;
procedure TForm1.W3Button4Click(Sender: TObject);
var
number, zero: Integer;
begin
// Raise an selection exception
try
zero := 1;
number := 0 div zero;
if number = 0 then
DoSomething;
if IsInfinite(number) then
raise Exception.Create('Division by zero')
else
begin
ShowMessage('number / zero = ' + FloatToStr(number));
end;
except
on e: EAbort do
ShowMessage('A different error occured: ' + e.message);
on e: exception do
ShowMessage('All other errors are handled by this one: ' + e.message);
end;
end;
|
Result is: It will show a message A different error occured: Abort without dialog
function W3Button4Click(Self, Sender$9) {
var number$4 = 0;
var zero$4 = 0;
try {
zero$4 = 1;
number$4 = $Div(0,zero$4);
if (!number$4) {
DoSomething();
}
if (IsInfinite(number$4)) {
throw Exception.Create($New(Exception),"Division by zero");
} else {
alert("number / zero = "+((number$4)).toString());
}
} catch ($e) {
if ($Is($e,EAbort)) {
var e$11 = $W($e);
alert("A different error occured: "+$Check(e$11,"").FMessage)}
else if ($Is($e,Exception)) {
var e$12 = $W($e);
alert("All other errors are handled by this one: "+$Check(e$12,"").FMessage)}
else throw $e
}
}
|
Ignoring exceptions
Unless you handle an exception it causes the execution of the current procedure (recursively back to the first caller) to be aborted. In other words, it will stop running your code and propagate the error upwards. So if you don’t handle errors, it will report it to the method that called your code, or exit that and continue until it finds an exception handler. If you simply want to ignore any errors and continue no matter what (not recommended) you can do as such:
Example code : Divide by zero with no message error
procedure TForm1.W3Button1Click(Sender: TObject);
var
number, zero : Integer;
begin
// Try to divide an integer by zero - to raise an exception
Try
zero := 0;
number := 1 div zero;
if IsInfinite(number) then raise Exception.Create('Division by zero') else
ShowMessage('number / zero = '+IntToStr(number));
Except
on E : Exception do; // Ignore any error and continue
end;
end;
|
Result is: neither messages nor console error
function W3Button1Click(Self, Sender$6) {
var number$1 = 0;
var zero$1 = 0;
try {
zero$1 = 0;
number$1 = $Div(1,zero$1);
if (IsInfinite(number$1)) {
throw Exception.Create($New(Exception),"Division by zero");
} else {
alert("number / zero = "+number$1.toString());
}
} catch ($e) {
var E = $W($e);
/* null */
}
}
|
And FINALLY ...
Suppose that instead of trapping the error where it occurs, you may want to let a higher level exception handler in your code to do a more global trapping. Delphi provides an alternative part to the exception wrapper the Finally clause. Instead of being called when an exception occurs, the finally clause is always called after part or all of the try clause is executed. However, it does not trap the error - the next highest exception handling (try) block that we are nested in is located and executed.
Example code : Zero divide with a finally clause
procedure TForm1.W3Button3Click(Sender: TObject);
var
number, zero : Integer;
begin
// Try to divide an integer by zero - to raise an exception
Try
zero := 0;
number := 1 div zero;
if IsInfinite(number) then raise Exception.Create('Division by zero') else
ShowMessage('number / zero = '+IntToStr(number));
Finally
if number = -1 then
begin
ShowMessage('Number was not assigned a value - using default');
number := 0;
end;
end;
end;
|
Result is:
Number was not assigned a value - using default
Then, the program terminates with an Uncaught #<Object> console message - the finally clause did not trap the error.
function W3Button3Click(Self, Sender$8) {
var number$3 = 0;
var zero$3 = 0;
try {
zero$3 = 0;
number$3 = $Div(1,zero$3);
if (IsInfinite(number$3)) {
throw Exception.Create($New(Exception),"Division by zero");
} else {
alert("number / zero = "+number$3.toString());
}
} finally {
if (number$3==(-1)) {
alert("Number was not assigned a value - using default");
number$3 = 0;
}
}
}
|
Example code : Zero divide with a finally clause and Except
procedure TForm1.W3Button2Click(Sender: TObject);
var
number, zero: Integer;
begin
// Try to divide an integer by zero - to raise an exception
try
try
zero := 0;
number := 1 div zero;
if IsInfinite(number) then raise Exception.Create('Division by zero')
else
ShowMessage('number / zero = ' + IntToStr(number));
except
number := -1;
end;
finally
if number = -1 then
begin
ShowMessage('Number was not assigned a value - using default');
number := 0;
end;
end;
WriteLn('number is: '+IntToStr(number));
end;
|
Result is:
Number was not assigned a value - using default
Then, the program display console message number is 0. The program trap the error and set number to -1
function W3Button2Click(Self, Sender$7) {
var number$2 = 0;
var zero$2 = 0;
try {
try {
zero$2 = 0;
number$2 = $Div(1,zero$2);
if (IsInfinite(number$2)) {
throw Exception.Create($New(Exception),"Division by zero");
} else {
alert("number / zero = "+number$2.toString());
}
} catch ($e) {
number$2 = -1;
}
} finally {
if (number$2==(-1)) {
alert("Number was not assigned a value - using default");
number$2 = 0;
}
}
WriteLn(("number is: "+number$2.toString()));
}
|
Try-Finally is normally used by a routine to allow cleanup processing to take place, such as freeing resources, with the exception being correctly passed to the caller to handle. In this example, a exception is trapped.
|