EXCEPT2


Document :: Exceptions Version 2
Author :: Kris Erickson
From "The Bits" Website
http://www.cbuilder.dthomas.co.uk
email to : Kris_Erickson@mindlink.bc.ca
Legal Stuff
This document is Copyright ©Kris Erickson July 1997
This release of the document ©The Bits They Forgot July 1997
You may redistribute this file FREE OF CHARGE providing you do not charge recipients anything for the
process of redistribution or the document itself. If you wish to use this document for educational purposes
you may do so free of charge.
However, If you wish to use all or part of this document for commercial training or commercial publication
you must contact the author as charges may apply.
You may not receive **any** moneys for this document without the prior consent of the author.
No liability is accepted by the author(s) for anything which may occur whilst following this tutorial
Greetings all. The last article dealt with exceptions in C++, this one will deal with exceptions in
Builder. This is part two of two.
Enjoy,
Kris...
Colour Coding:
Information displayed in this colour is information provided by my copy of BCB.
Information displayed in this colour is information you need to type.
Information displayed in this colour is information which is of general interest.
Code displayed in this colour is not meant to be typed in, but only included only a an example.
********************************************************************************
Part 2
Exceptions in Builder
Well, now that we have discussed what exactly exceptions are, how does that relate to
how we use them in Builder. Do we use them in the same way as we would in regular C++ (most
of you probably noticed that in part 1 I didn't include any demonstrations that were based around
Builder)? Well, yes and no. You could, if you wanted to, use exceptions just as you would C++
but you would be missing all of what Builder offers to aid us in the use of exceptions.
If you remember the last article I designed a very simple exception class, which I called
ExceptionObject, from which I created a descendant class (sub class, child class) FileException
which knew how to handle file exceptions. Well, in Builder the programmers at Borland have
presented you with a very large and robust set of exception classes based around the class
Exception. And VCL objects are programmed to generate exceptions for most of the errors that
will occur while they are executing. All of these classes are nice, but they do seem a little
imposing to the programmer who is using them for the first time. With such a copious list of
exceptions (and a severe lack of documentation) it becomes very difficult to determine which
exception to use (do you use EDivByZero or EZeroDivide to catch division by zero? - Depends
on whether you are using int's or floats...) Anyhow, once one becomes familiar with the
exceptions (after all, while running the program Builder will tell you which unhandled exception
was produced) you will wonder how you ever debugged without them.
Before we do too much here, let's set up the Builder environment so that we can receive
exceptions as a user would... Set the Options | Environment | Preferences | Break on Exception
to false. When Break on Exception is set to true, the debugger (the program which runs your
program inside of Builder) will halt the program when an exception is thrown. Isn't that what we
want? Well, once again, yes and no. We want the program to halt, but after it has executed our
exception handling code, if Break on Exception is set to true it breaks before our exception
handling code (you can select continue from Run menu if you want, but I find it easier to change
the preferences). Let's create a quick example of where we use some of Builders built in
exceptions .
Create a new project, and drop three Labels, three Edits, and a button on to the form.
Set their properties to the following:
object Form1: TForm1
Caption = 'Division Calculator'
object Label1: TLabel
Left = 104
Top = 57
Width = 66
Height = 16
Caption = 'Numerator:'
end
object Label2: TLabel
Left = 104
Top = 109
Width = 80
Height = 16
Caption = 'Denominator:'
end
object Label3: TLabel
Left = 105
Top = 160
Width = 41
Height = 16
Caption = 'Result:'
end
object Edit1: TEdit
Left = 201
Top = 53
Width = 121
Height = 24
TabOrder = 1
end
object Edit2: TEdit
Left = 201
Top = 105
Width = 121
Height = 24
TabOrder = 2
end
object Edit3: TEdit
Left = 201
Top = 156
Width = 121
Height = 24
TabStop = False
TabOrder = 0
end
object Button1: TButton
Left = 176
Top = 210
Width = 75
Height = 25
Caption = 'Divide'
TabOrder = 3
end
end
The form should look something like figure 1:
figure 1.
Add a message handler for the button.
void __fastcall TForm1::DivideClick (TObject *Sender)
{
float result;
result = StrToFloat(Edit1->Text) / StrToFloat(Edit2->Text);
Edit3->Text = result;
}
Save the unit as ExceptForm.cpp and the project as ExceptProj.cpp and then see what happens
when we run it. Well it works fine, except (pardon the pun) that when we enter 0 in the
denominator, or a non-number into one of the labels we get an exception thrown. Well, it doesn't
appear to the user as an exception but just as a message unless you haven't yet turned off break
on exceptions in which case you will see that an exception was thrown. Actually, Builder itself
does give fairly specific error messages to the user without us programmers having to do a thing.
But since we are programmers, we want the power to be able to change what Builder generically
gives. Well, so now what do we do? Well given that this is Builder, and it has powerful features
like MaskEdit, we could change the labels to edit masks and force the user not to enter a 0 or a
character. Another option (and one that would be on topic) would be to reject non valid entries
and give the user a friendly warning about that (perhaps allowing them to re-enter the data).
void __fastcall TForm1::DivideClick (TObject *Sender)
{
float result;
try
{
result = StrToFloat(Edit1->Text) / StrToFloat(Edit2->Text);
Edit3->Text = result;
}
catch (EZeroDivide &Ex)
{
Beep(10000,1000);
MessageDlg("You cannot divide by Zero!\r"
"Re-enter the denominator and try again.",mtError,
TMsgDlgButtons() << mbOK, 0);
}
catch (EConvertError &Ex)
{
Beep(10000,1000);
MessageDlg("Both the numerator and the denominator have to be valid"
"numbers. Check numerator and denominator and try again.",mtError,
TMsgDlgButtons() << mbOK, 0);
}
}
Some things to note about the above code: If you haven't used Builders MessageDlg before
you might notice the strange argument {TMsgDlgButtons() << mbOK}. This just creates a set
based on TMsgDlgButtons and adds mbOK (the OK button) to the set (for more information on
sets see the tutorial on Sets.at The Bits They Forgot) The beep is just to make it seem more
like an error (it felt wrong when it was so quiet!) It is important to understand that Builder
exceptions have to be passed by reference. In regular C++ you can get away with passing an
exception class (or any class, for that matter) by value (however, notice in the first part of this
tutorial that I didn't) but in Builder you cannot. Exception is a VCL object, and therefor must be
allocated dynamically on the Stack. In C++ you are allowed to pass any class you want by
value, but it is almost always a better idea to pass you class by reference since creating a local
variable, especially a class, has considerable overhead required (since this is an article on
Exceptions and not on C++ and classes I won't get in to where it is a good idea to pass a class
by value, but remember you have to have a copy constructor for the class if you do...)
Whew, that was a bit of an unintentional digression. All right, in case you haven't already, you
should run your new form and test it trying to make it crash. It may not be 100% perfect but it
will give us slightly more friendly messages when we run into errors. That is really all that there
is to handling Builders built in exceptions. A few quick notes, though, before we get on to
throwing our own exceptions.
The Mother of all Try - Catches
In case you hadn't gone wondering through you program files yet, you might not have noticed
that all Builder programs are incased in a giant Try - Catch statement:
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
try
{
Application->Initialize();
Application->CreateForm(__classid(TForm1), &Form1);
Application->Run();
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
return 0;
}
This is done for many reasons, most of which I won't go into, however it does allow the re-
throwing of exceptions with the guarantee that they will be eventually caught. Why would you
want to re-throw an exception, you ask. Well suppose you used the exception to be certain that
if an error occurred you could free up some memory that had to be de-allocated, and then let
Builder do it's default exception handling. There are other examples, but you will probably come
across them yourselves when you are programming. It is more important at this point to know
how, rather than why you would wish to re-throw an exception. And the code to do it is simplicity
itself: you just add the keyword throw as the last statement in your exception handler. For
example:
catch (EZeroDivide &Ex)
{
MessageDlg("You cannot divide by Zero!\r" +
"Re-enter the denominator and try again.",mtError,
TMsgDlgButtons() << mbOK, 0);
throw;
}
The final throw will re-throw the same exception as was caught by the catch statement (in the
above example you will get different Message dialogs, probably not what you wanted but it is just
an example of how you throw an exception, and to prove the point that the same exception is re-
thrown.
Getting The Name of the Exception Class
One of the nice characteristics of Exception is that is derived from TObject*** and
therefor knows it's ClassName. You can create generic exception handilng routines for errors
that use this knowledge, that may (note that I said may here, generally Builders built in
exceptions are very helpful and informative) be even more helpful than the built in exception
handlers. For example:
try
{
somthing();
}
catch (Exception &Ex)
{
MessageDlg("Exception type " + Ex.ClassName() + " returned error message:
"
+ Ex.Message + "\r" + "Which occurred at line: " +
AddressToStr(ExceptAddr) + " in form " + this->ClassName(), mtError,
TMsgDlgButtons() << mbOK, 0);
}
Looking At Exception and its Descendants
You can learn a lot just looking at the header file for Exception. It is readily available to users
of Builder (even those of us with the standard edition) in the SYSUTILS.HPP file. If you look at
it, there really isn't a lot to it except a bunch of overloaded constructors. And realistically (unless
you want to become some kind of inhuman exception God) you really only need to know maybe
2 of them (most importantly the constructor which takes an AnsiString, but we will talk about that
shortly). What is good to know and have a list of all of the descendants of exception. Although
they are available in the help(I have yet to find a plain exception list in help, but I find that if you
go to help and hit the Find tab and type in exception highlight exception and then scroll until you
get the E's you will find all the exceptions there), it is useful to have a list of them handy:
Exception - The base class. A generic error (can be used to catch any
exception)
Eabort - EAbort is used to raise an exception without displaying an error
message in a dialog box.
EOutOfMemory - Cannot allocate memory on the heap.
EInOutError - An operating-system file i/o error occurs and the I/O checking
option is selected (see help for more info). error.
EIntError - Generic integer math exception. Base class for EDivByZero,
ERangeError, EIntOverflow.
EDivByZero - Integer division by zero.
ERangeError - Integer range error.
EIntOverflow - Integer overflow.
EMathError - Generic floating point math exception. Base class for
EInvalidOp,
EZeroDivide, Eoverflow, Eunderflow.
EInvalidOp - Invalid floating point op.
EZeroDivide - Floating point division by zero.
Eoverflow - Floating point overflow.
Eunderflow - Floating point underflow.
EInvalidPointer - Invalid pointer when a program tries to delete of the same
pointer twice, or refers to a pointer deleted pointer.
EInvalidCast - A cast which was not allowed.
EConvertError - Attempt to convert a string or object improperly.
EAccessViolation - Program dereferences a NULL pointer, etc.
Eprivilege - Attempt to access processor options which are not available (PPro
commands on a Pentium, etc).
EStackOverflow - Stack overflowed.
EControlC - Raised when user presses control-c in a console application.
EVariantError - Varient error (see upcoming Bit's They Forgot tutorial on
variants)
EPropReadOnly - Attempt to write to a read only property.
EPropWriteOnly - Attempt to read from a write only property.
EExternalException - See below.
EInvalidArgument - EInvalidArgument is raised by some functions in math.pas.
EInvalidGraphic - An invalid graphic was assigned to a bitmap.
EFOpenError - When an error occurs while opening a file with a VCL streaming
function.
EFCreateError - The exception class for streaming file-creation errors of VCL
functions.
This is not an exhaustive list, but it mentions a lot of the more used exceptions (but definitely not
in their order of use). Personally I have them printed out and it floats around my desk (with other
useful floating sheets, like the ASCII and ANSI keycodes, the Builder Inheritance sheet, and
EMACS key sheet and ... uh oh I was digressing again). The more familiar you
become with Exception and its descendants classes the better you will be with exceptions, and
the better you are with exceptions, the faster you will be able to churn out bug free code. Even if
you only use Builders built in exception handling it is very useful to know exactly what Builder is
telling you when it spits out an exception error.
It is important to remember that
Builder exceptions are available when you are doing file operations with VCL classes (like
TStrings->LoadFromFile, for example) but not if you are using standard C++ file i/o (fstream,
stdio, etc).
Throwing Builder Exceptions
Throwing an exception in Builder ranges from very easy, to strangely obscure, and not really
worth the effort of learning (I shun variant arrays like the plague and only use them when I
REALLY have to.) Just in case you are interested below are the constructors for Exception:
__fastcall Exception(const System::AnsiString Msg);
__fastcall Exception(const System::AnsiString Msg, const System::TVarRec
*Args,
const int Args_Size);
__fastcall Exception(int Ident);
__fastcall Exception(int Ident, const System::TVarRec *Args, const int
Args_Size);
__fastcall Exception(const System::AnsiString Msg, int AHelpContext);
__fastcall Exception(const System::AnsiString Msg, const System::TVarRec
*Args,
const int Args_Size, int AHelpContext);
__fastcall Exception(int Ident, int AHelpContext);
__fastcall Exception(int Ident, const System::TVarRec *Args, const int
Args_Size, int AHelpContext)
The only one we are going to be using is the first one, but feel free to look at the help and work
out how to use any of the other constructors (you're on your own for that one though...)
Lets assume we were writing our own little hex numbers class (a classroom favorite!)
and we want to throw an error because the person has entered a 'G', which is obviously not a hex
number.
Hex Hex::GetNum(AnsiString NewHex)
{
// Various code goes here
if (NotValid(CurrentDigit))
{
Exception* NewException = new Exception(CurrentDigit +
" is not a valid hex digit!");
throw NewException;
}
}
and then we can simply catch this as generic Exception with the following code:
// Code goes here
try
{
AddHex.GetNum("341G");
}
catch (Exception &Ex)
{
ShowMessage("Exception occurred, error message: " + Ex.message);
}
I know that a bunch of you are screaming about the way I threw the exception, but I wanted to
point out exactly what is getting done when the exception is being thrown. A new exception
object is created (instantiated) and then that object is passed down to the catch function
(Actually, it is quite interesting how the exceptions mechanisms work in C++. Try to figure out
where the destructor is called?) If you want to save some finger exercise and type the shortcut,
here it is:
Hex Hex::GetNum(AnsiString NewHex)
{
// Various code goes here
if (NotValid(CurrentDigit))
{
throw Exception(CurrentDigit + " is not a valid hex digit!");
}
}
I personally use the shorter method, as I find it slightly clearer to read what is going on at the
code level, however the longer method is easier to understand what is going on at the compiler
level (the compiler will generate exactly the same code for both of these versions)
All right, so we have are GetNum routine all written (OK, pretend that we have our GetNum
routine written), and assume that most of the other routines are written save the division routine.
Like all programmers we have had the division by zero check rammed in our head so frequently,
that we know we are going to need to throw a EDivByZero:
Hex Hex::Operator/(Hex denominator)
{
Hex result;
if (denominator == 0) // We have already overload the == operator...
{
throw EDivByZero("Division by Zero occurred in Hex::Operator/");
/* note: This is just the same as
EDivByZero* DivException = new
EDizeByZero("Division by Zero occurred in
Hex::Operator/");
throw DivException;
*/
}
else
{
result = this->intEquiv / denominator.intEquiv;
}
return result;
}
Actually we should probably write our own EHexDivByZero class which is descended from
EHexError (another class we would have to create), and start adding our own exception library to
the standard Borland exceptions:
class EHexError : public Exception
{
public:
__fastcall EHexError(const AnsiString Message):Exception(Message){}
};
class EHexDivByZero : public EHexError
{
public:
__fastcall EHexDivByZero(const AnsiString Message):EHexError(Message){}
};
Now these exception classes wouldn't have all the functionality of the standard exception
classes, but they would help to create a more complete class Hex class...
Catching Non VCL Exceptions with EExternalException
You can catch non VCL exceptions with the EExternalException class. Don't ask me how it
works (actually if anyone understands how EExternalException works please e-mail me, I would
like to know...) as I don't know, but it does work and it can be useful sometimes if you want to
quickly track down an error, or you are using code inherited from standard C++ that threw
exceptions. It won't tell you a heck of a lot, but it may be useful...
If you want to play with it, add another button to the form you built earlier and add this code for
it...
void __fastcall TForm1::Button2Click(TObject *Sender)
{
try
{
throw "Test of this!";
}
catch (EExternalException &Ex)
{
MessageDlg(Ex.Message,mtError,TMsgDlgButtons() << mbOK, 0);
}
}
That's really all there is to exceptions. Use them and you'll make your life easier, but even if you
don't and you at least understand what is going on I think you'll know a little more about how
Builder works...
See ya next time...
Kris
The author would appreciate any comments on this article, and would like to thank the authors of
all of the copious letters he has received for the previous articles... Thank you, I have learned a
lot from all of your insightful comments, and criticisms. (And have been flattered to no end by
your kind compliments.)
Kris
kson@istar.ca
No liability is accepted by the author(s) for anything which may occur whilst following this tutorial


Wyszukiwarka

Podobne podstrony:
ExceptionDetailMessage
rmi exceptions5
function java last exception clear
exceptions
rmi exceptions7
exceptions
exceptions
rmi exceptions2
java lang Exception
function satellite caught exception
les08 except showme
exceptions
java lab09 exception
ExceptionDetailMessage
EXCEPT1 id 2053264 Nieznany
exceptions doc
ExceptionListener

więcej podobnych podstron