In classic C++, exceptions could be thrown by value, which would result in a call to the copy constructor for the exception object.. catch Exception^ exception{ // code to handle the ex
Trang 1In this chapter, you’ll begin by looking at aspects of exception handling in C++/CLI that are
not present in classic C++ Then you’ll look at attributes, which supply metadata for a type and,
although not part of standard C++, may be familiar if you’ve used previous versions of Visual C++
You’ll learn how to use the existing NET Framework attributes, examine some of the common
ones, and look at how to define and use your own attributes Finally, you’ll get a brief overview
of the reflection features of the NET Framework, which provide a way to discover information
on a type at runtime and use that information to interact dynamically with a type
Exceptions
Exceptions are supported in classic C++, but not universally used In NET Framework
programming, exceptions are ubiquitous, and you cannot code without them This chapter
assumes you are aware of the basic concepts of exception handling, throwing exceptions, and
the try/catch statement All of these features of classic C++ are valid in C++/CLI code
A key difference between exception handling in C++/CLI and in classic C++ is that
excep-tions are always thrown and caught by reference (via a handle), not by value In classic C++,
exceptions could be thrown by value, which would result in a call to the copy constructor for
the exception object In C++/CLI, exceptions are always on the managed heap, never the stack
Therefore, you must use a handle when throwing a C++/CLI exception, as in Listing 10-1
Listing 10-1 Throwing an Exception
Trang 2catch( Exception^ exception)
{
// code to handle the exception
}
The Exception Hierarchy
All NET exceptions inherit from a single root class, System::Exception Table 10-1 shows some
of the common exceptions thrown by the runtime in C++/CLI code
Table 10-1 Some Common NET Framework Exceptions
System::AccessViolationException Thrown when an attempt to read or write
protected memory occurs
System::ArgumentException Thrown when an argument to a method is
not valid
System::ArithmeticException Thrown when an error occurs in an arithmetic
expression or numeric casting operation This is
a base class for DivideByZeroException, NotFiniteNumberException, and OverflowException
System::DivideByZeroException Thrown when division by zero occurs
System::IndexOutOfRangeException Thrown when an array access out of
bounds occurs
System::InvalidCastException Thrown when a cast fails
System::NullReferenceException Thrown when a null handle is dereferenced or
used to access a nonexistent object
System::OutOfMemory Thrown when memory allocation with
gcnew fails
System::TypeInitializationException Thrown when an exception occurs in a static
constructor but isn’t caught
Trang 3Listing 10-2 Using the Properties of the Exception Class
Console::WriteLine("Exception Source property {0}", exception->Source);
Console::WriteLine("Exception StackTrace property {0}",
exception->StackTrace);
Console::WriteLine("Exception Message property {0}", exception->Message);
}
}
The output of Listing 10-2 is as follows:
Exception Source property exception_properties
Exception StackTrace property at main()
Exception Message property XYZ
When an unhandled exception occurs in a console application, the Message and
StackTrace data are printed to the standard error stream, like this:
Unhandled Exception: System.Exception: XYZ
at main()
There’s also a property of the Exception class called InnerException, which may reference
an exception that gives rise to the exception we’re looking at In this way, a cascading series of
exceptions may be nested one within the other This could be useful if an exception occurs
deep down in low-level code, but there are several layers of libraries between the problem and
the code that knows how to handle such situations As a designer of one of the intermediate
libraries, you could choose to wrap that lower exception as an inner exception and throw a
higher exception of a type that is more intelligible to your clients By passing the inner exception,
Trang 4the inner exception information can be used by the error-handling code to respond more appropriately to the real cause of the error.
Creating Exception Classes
You will often want to create your own exception classes specific to particular error conditions; however, you should avoid doing this and use one of the standard Exception classes, if possible Writing your own exception class lets you filter on and write exception handlers specific to that error To do this, you may derive from System::Exception You would normally override the Message property in the Exception base class to deliver a more relevant error message (see Listing 10-3)
Listing 10-3 Creating a Custom Exception
// exceptions_custom.cpp
using namespace System;
ref class MyException : Exception
// The first catch blocks are the specific exceptions that
// you are looking for
catch (MyException^ e)
{
Console::WriteLine("MyException occurred! " + e->Message);
}
Trang 5// You may also catch other exceptions with multiple try blocks,
// although it's better
catch (Exception^ exception)
{
Console::WriteLine("Unknown exception!");
}
}
The output of Listing 10-3 (with no command-line arguments) is shown here:
MyException occurred! You must supply a command-line argument
Using the Finally Block
C++/CLI recognizes the finally contextual keyword, which is a feature of other languages that
support exception handling such as Java and C# The finally keyword precedes a block of code
known as a finally block Finally blocks appear after catch blocks and execute whether or not an
exception is caught
Use a finally block (see Listing 10-4) to put any cleanup code that you don’t want to duplicate
in both the try block and the catch blocks The syntax is like that in other languages
Listing 10-4 Using a Finally Block
Trang 7Dealing with Exceptions in Constructors
A difficult problem in any language is what to do with objects that fail to be constructed
prop-erly When an exception is thrown in a constructor, the result is a partially constructed object
This is not a largely theoretical concern; it’s almost always possible for an exception to be thrown in
a constructor For example, OutOfMemoryException could be thrown during any memory
alloca-tion The finalizer will run on such partially constructed objects In C++, destructors do not run
on partially constructed objects The finalizer is called by the runtime to clean up before the
runtime reclaims the memory As usual, the execution of the finalizer is nondeterministic, so it
won’t necessarily happen right away, but will happen eventually This is another reason to write
finalizers carefully, without assuming any objects are valid In Listing 10-6, an exception is
thrown in the construction of a member of A in A’s constructor The finalizer is called to clean
up; the destructor is not called
Listing 10-6 Throwing an Exception in a Constructor
// exceptions_ctor.cpp
using namespace System;
// the type of the member
ref class Class1
{
public:
Class1()
{
// Assume a fatal problem has occurred here
throw gcnew Exception();
Trang 8// c2 never gets created.
Throwing Nonexception Types
C++/CLI allows you to throw objects that are not in the exception class hierarchy If you’ve done a lot of programming in C# or Visual Basic NET, this may be somewhat of a surprise, since in those languages, you are limited to throwing exception objects that derive, directly or indirectly, from System::Exception In C++/CLI, you’re not limited in this way However, if you are calling C++/CLI code from C# or VB NET code, and an exception object of an unusual type
is thrown, it will be wrapped in an exception from the point of view of the C# or VB NET code.The basic idea is simple, as Listing 10-7 shows
Listing 10-7 Throwing an Object That’s Not an Exception
// throw_string.cpp
using namespace System;
public ref class R
{
Trang 9The subtlety arises when you run this C++/CLI code from another language If the code in
Listing 10-7 is compiled to a DLL assembly and reference in C#, and you call the R::TrySomething
method, a RuntimeWrappedException object is created
Note that cross-language work is best done in the Visual Studio IDE, so you can be sure
that the right references, assembly signing, and manifests are all done properly Create two
projects in the same solution (see Listing 10-8) Set the C# project as the startup project, and
configure the C++/CLI project as a DLL Reference the C++/CLI project from the C# project,
Trang 10The output of Listing 10-8 is as follows:
An object that does not derive from System.Exception has been wrapped in a
RuntimeWrappedException
Error that throws string!
I do not recommend throwing nonexception objects Throwing exception objects that all derive from the root of the same exception hierarchy has the advantage in that a catch filter that takes the Exception class will capture all exceptions If you throw objects that don’t fit this scheme, they will pass through those filters There may be times when that behavior is desired, but most of the time you are introducing the possibility that your nonstandard exception will
be erroneously missed, which would have undesired consequences (The paradox is that a non-Exception exception is an exception to the rule that all exceptions derive from Exception You can see how confusing it could be.)
Unsupported Features
Exception specifications are a C++ feature that allow a programmer to declare what exceptions
a particular function can throw This is intended as a heads-up to users of a function that they should be prepared to deal with these exceptions Exception specifications are not supported
in Visual C++ even in native code, and C++/CLI does not support this feature either In general, this feature is impractical because it is not usually feasible to list the complete set of exceptions that a given block of code might generate, most particularly exceptions that propagate from any function called that doesn’t have exception specifications Furthermore, some common exceptions, such as OutOfMemoryException, could be generated almost anywhere Should these
be included in all exception specifications? Another problem is performance, since this feature adds to the already intensive runtime overhead associated with exception handling For all these reasons, the designers of the CLI chose not to implement this feature
Exception-Handling Best Practices
Exception handling is controversial All aspects of exception handling, it seems, are up for debate Regardless of what your position is, one thing remains certain: if your framework uses
exceptions, you, too, must use exceptions For CLI types, there is no option not to use exception
handling However, you must use it sensibly and with restraint Exceptions should not be used
in normal flow control, because they do incur a significant performance penalty when thrown and caught Exceptions should be used for truly exceptional conditions, errors that would not
be expected from normal, correct program functioning
Trang 11Here are some best practices for handling exceptions:
Avoid unnecessary proliferation of exception types If an appropriate NET Framework
standard exception exists, then it should be used For example, if you are reporting invalid
arguments to a function, you should throw ArgumentException, not an exception of your
own making It is appropriate to define your own exception when it is important to filter
on that exception and respond uniquely to it
Throw and catch specific exceptions, not the System::Exception class at the root hierarchy
Also, catch blocks should be ordered so that you catch the most specific exceptions first,
followed by more general exceptions If you do both of these things, you can write code
that knows how to handle the specific exceptions and be sure that it is called when those
specific errors occur
Catch only those exceptions that you can reasonably handle Any exceptions that your
code at this particular level in the application doesn’t know how to handle should be allowed
to propagate up the chain, rather than “recovering” from an exception and attempting to
continue when complete recovery isn’t possible This poor practice is known as swallowing
errors It’s usually better to bring down an application with an unhandled exception than
to continue in an unknown state
Put cleanup code in the finally block, rather than in the catch block The catch block is for
handling and recovering from the error, not cleaning up
When rethrowing exceptions in a catch block, use the throw statement without providing
the exception object This is interpreted correctly by the runtime as continuing the
propaga-tion of the same exceppropaga-tion, rather than starting a new exceppropaga-tion The complete call stack is
then preserved in the exception’s StackTrace property
Much more could be said about exception-handling best practices, and since exception
handling is common to many languages, guidance in one language often applies to all languages
I’ve only scratched the surface here There are many resources available to help use exceptions
properly See, for example, Framework Design Guidelines: Conventions, Idioms, and Patterns
for Reusable NET Libraries by Krzysztof Cwalina and Brad Abrams (Addison-Wesley, 2005).
Exceptions and Errors from Native Code
When dealing with an application that includes native code and managed code, you will be
dealing with potentially many different types of error codes and exceptions In addition to
C++/CLI exceptions, you will have C++ exceptions, COM and Win32 error codes, and possibly
structured exceptions, which are a Microsoft-specific extension to C You also have to deal with
all the error codes and exceptions in libraries that you’re using Exceptions from native code
are wrapped in managed exceptions Also, error codes from COM (HRESULTs) are wrapped in
exceptions when they propagate to managed code While I cannot go into all the details behind
dealing with these diverse situations in this introductory text, you’ll learn some of the basics in
Chapter 12
Trang 12Literally, metadata means “data about data.” Attributes represent metadata for the program
element to which it is applied Attributes may be applied to many program elements, including assemblies, classes, constructors, delegates, enumerated types, events, fields, interfaces, methods, portable executable file modules, parameters, properties, return values, structures, or even other attributes
In the context of this discussion, metadata means data that is not directly part of whatever
it is applied to In this sense, attributes allow information to be associated with program ties without affecting the internal structure of the entity For example, an attribute naming the author of a type is not really part of the internal structure of a type Attributes for program elements may be queried at runtime, so programs may make use of the metadata to manipulate objects
enti-The program element to which an attribute is applied is referred to as the attribute target.
How Attributes Work
Attributes are classes defined either in the NET Framework BCL or another library You can define your own attributes as well by creating a class that derives from another attribute The attribute class contains the data to be associated with a program element Attributes are then
applied to program elements using a syntax involving square brackets, called an attribute
speci-fication, as follows:
[ SomeAttribute(arguments) ]
If the attribute doesn’t take any arguments, you can omit the parentheses entirely.Where the attribute is placed in the code often determines the target to which the attribute applies When applied to an entity that takes modifiers, it precedes all modifiers
[ MethodAttribute(arguments) ]
public static int SomeMethod([ParameterAttribute] param1);
Multiple attributes may be specified in a single pair of square brackets or may appear in sequential square brackets
[ returnvalue: ReturnValueAttribute(arguments) ]
ReturnType^ GetValue();
If unspecified, the preceding attribute would be applied to the method, not the return value
Trang 13The Attribute Class
All attributes inherit from the System::Attribute class, either directly or indirectly By
conven-tion, attribute classes have Attribute as a suffix The Attribute suffix may be omitted when
referring to the name of the attribute in an attribute specification
Attribute Parameters
Attribute constructors may take arguments These arguments are passed in to the attribute’s
class constructor when it is applied to a program element There are two ways to pass in the
constructor arguments: the usual way involving the order of the parameters, and a second way
in which the name of the parameter is given and the assignment operator is used to specify the
value The two methods are illustrated here:
[ SomeAttribute( "AttributeValue1", 200) ] // Positional parameters
[ SomeAttribute( Value = "AttributeValue1", IntegralValue = 200) ] // Named
parameters
Because attribute parameter evaluation occurs during startup, when the CLI
program-ming environment is not yet fully initialized, the language design imposes restrictions on the
types that may be used as attribute parameter types Attribute parameter types are restricted to
primitive types, string handles, object handles, handles to the Type class, enum classes that are
publicly accessible (and, if nested, are nested in a publicly accessible type) as well as
one-dimensional managed arrays of these types These restrictions are in place in order to ensure
that the runtime has access to the types when it needs them and that there are no
dependen-cies on additional external assemblies
Some Useful Attributes
The NET Framework contains many attributes Let’s look at a few of them
The Obsolete Attribute
The Obsolete attribute is one of the simplest of attributes Try compiling the code in Listing 10-9
Listing 10-9 Using the Obsolete Attribute