Introduction
The managed extensions to C++ extends traditional C++ exception handling
capabilities and adds support for managed exception handling. This article does
not cover traditional exception handling or structured exception handling, and
is only meant as a guide to handling managed exceptions in an application that
makes use of the managed extensions to C++. Managed exceptions are simply
exceptions thrown by managed types (typically by classes in the BCL) and it's
recommended that you use the System::Exception
class as your exception object,
either directly or by creating your custom exception classes using this class as the base class.
While there is nothing preventing you from throwing managed objects that are not
derived from System::Exception
; one slight point to note is that you can only
throw __gc
objects, which basically means you cannot throw __value
objects
directly (but you can box them and throw the boxed object).
Note - In all the example code snippets, you'll see a Show
function
which is not really a function, but a preprocessor definition I added as follows
:-
#define Show Console::WriteLine
Throwing exceptions
Managed extensions are thrown using the throw
statement.
void ThrowFunction()
{
Show("in ThrowFunction");
throw new Exception("asdf");
}
In the above example I have thrown an exception of type
System::Exception
. System::Exception
is the
recommended base class for all managed exception classes and the .NET BCL
classes exclusively use this class or one of several derived classes for their
exception handling requirements. The output of calling the above function would
be something very similar to :-
in ThrowFunction
Unhandled Exception: System.Exception: asdf
at ThrowFunction() in c:\...\exceptionsdemo.cpp:line 23
at main() in c:\...\exceptionsdemo.cpp:line 138
Since we haven't handled the exception the CLR has handled it for us and it
displays the exception text message that we passed through the constructor
overload that we used to create the exception. Essentially the
Exception
constructor
that accepts a
System::String
just saves the passed string to a member variable called
_message
and this is then exposed through a read-only property called
Message
. In addition the CLR also prints out a stack trace by using the
StackTrace
property of the
Exception
class. So we can imagine that the CLR puts a catch-all
exception handler for all IL executables that run under it, and for caught
exceptions it just prints the text in the
Message
and
StackTrace
properties of the
Exception
object that was thrown. Of course this is an over-simplistic
projection of the CLR's activities, and the actual CLR processes might be
extensively more complicated than this humble visualization.
Handling exceptions
We handle exceptions by using try
-catch
blocks.
void TryFunction()
{
try
{
Show("in TryFunction");
ThrowFunction();
Show("this won't come");
}
catch( Exception* e)
{
Show("in TryFunction exception block {0}",e->Message);
}
}
The output of the above program will be similar to :-
in TryFunction
in ThrowFunction
in TryFunction exception block asdf
What happens is that the code inside the try
block
gets executed till an exception occurs or the end of the try
block is reached, and if an exception occurs then control jumps to the
catch
block. Any code remaining in the try
block
will not get executed as is obvious from the output of the above code snippet.
The ThrowFunction
function is the same as in the earlier
code snippet and we display the exception message using the
Message
property of the System::Exception
class.
One issue that might creep up with the above scenario is when you need to perform clean-up
operations. Let's say you have opened a disk file and while you are writing to
the disk-file an exception is raised, which means control has jumped to the
catch
block. Now the code that closes the disk-file does not get executed. A
rather inelegant solution is to close the file outside the try
-catch
block :-
if( file_is_open )
{
}
Now this might seem okay at first glance, but assume that you have nested
try
-catch
blocks (which is
quite normal when you write some proper code), now you'll need to be careful
where exactly you free your resources and you might also have to increase the
scope of temporary resource handle variables to allow them to be accessed
outside the try
-catch
blocks.
The solution to this issue is the __finally
keyword
used to create __finally
blocks which get executed
irrespective of whether or not an exception has been raised.
void TryFunction()
{
try
{
Show("in TryFunction");
ThrowFunction();
}
catch( Exception* e)
{
Show("in TryFunction exception block {0}",e->Message);
}
__finally
{
Show("in TryFunction Finally block");
}
}
Now try running the above code, as such, once, and then run it again after
commenting out the call to the ThrowFunction
function
in the try
block. You'll see that in both cases
the code inside the __finally
block does get executed.
Rethrowing exceptions
When you catch an exception inside a try
-catch
block there is a small issue -
any try
-catch
block that is higher up in the try
-catch
nested hierarchy will not
see this exception. Luckily it's quite easy to rethrow an exception.
void TryFunction()
{
try
{
Show("in TryFunction");
ThrowFunction();
}
catch( Exception* e)
{
Show("in TryFunction exception block {0}",e->Message);
throw;
}
__finally
{
Show("in TryFunction Finally block");
}
}
void TryFunction2()
{
try
{
Show("in TryFunction2");
TryFunction();
}
catch(Exception* e)
{
Show("in TryFunction2 exception block {0}",e->Message);
}
__finally
{
Show("in TryFunction2 Finally block");
}
}
We simply use the
throw
statement without any parameters inside the inner
catch
block. This will propagate the same exception object to the outer
try
-catch
block as is evident from the output of
calling the TryFunction2
method.
in TryFunction2
in TryFunction
in ThrowFunction
in TryFunction exception block asdf
in TryFunction Finally block
in TryFunction2 exception block asdf
in TryFunction2 Finally block
If you comment out the throw
statement in the inner
catch
block, you'll see that the outer catch
block is never reached because the exception was handled in the inner
try
-catch
block, but both
__finally
blocks do get executed as expected.
Using custom exception classes
You can create your own exception classes with extra properties and methods
to suite your requirements and it's strongly recommended that you derive them
from
System::Exception
. You must also remember to explicitly specify the
corresponding base class constructor for each overload of the constructor that
you are going to implement for your custom exception class.
public __gc class MyException : public Exception
{
public:
MyException() : Exception()
{
}
MyException(String* str) : Exception(str)
{
}
private:
};
void ThrowFunction()
{
Show("in ThrowFunction");
throw new MyException("asdf");
}
void TryFunction()
{
try
catch( MyException* e)
{
Show("in TryFunction exception block {0}",e->Message);
throw;
}
}
void TryFunction2()
{
try
catch(MyException* e)
{
Show("in TryFunction2 exception block {0}",e->Message);
}
}
I have simply derived a class called
MyException
from
System::Exception
and have overridden two of the overloaded constructors
of the base class. And in my catch blocks I now specify
MyException
as the type of exception object that is to be caught.
Multiple catch blocks
It's possible to have multiple
catch
blocks in any
try
-catch
-__finally
block of code, which we can take advantage to specifically handle multiple
exception types that get thrown. Let's write a function that throws different
types of exceptions based on a parameter we pass to it :-
void ThrowFunction2(int i)
{
Show("in ThrowFunction2");
if( i == 0 )
throw new MyException("asdf");
else
if( i == 1 )
throw new Exception("asdf");
else
throw new Object();
}
Now we can individually handle these separate exception scenarios as follows
:-
void TryFunction3(int i)
{
try
{
Show("in TryFunction3");
ThrowFunction2(i);
}
catch( MyException* e)
{
Show("in TryFunction3 my exception block {0}",e->Message);
}
catch( Exception* e)
{
Show("in TryFunction3 exception block {0}",e->Message);
}
catch( Object* o)
{
Show("in TryFunction3 exception block {0}",o->ToString());
}
__finally
{
Show("in TryFunction3 Finally block");
}
}
Remember to put the catch
blocks in reverse order of
inheritance hierarchy, as otherwise the upper-classes in the inheritance chain
will get swallowed up by the lowest class in the hierarchy or any classes lower
than itself in the inheritance chain. For example, in the above code
snippet, had we put the Exception
catch
block above the MyException
catch
block it would then catch both Exception
type exceptions and MyException
type exceptions, which
would be wholly undesirable, because the specific MyException
catch
block never gets executed.
You'll find quite frequently that several of the derived exception classes in
the .NET Base Class Library do not add any functionality to the
System::Exception
class. This is because these derived classes serve only
to isolate specific exceptions rather than actually add any extra methods or
properties to the Exception
class. Let's assume that we are
going to open a file and then write some information into it. Now assuming that
exception scenarios might arise either when opening a file or when writing to
the opened file, we would obviously need to handle them differently, as in the
slightly contrived code snippet below.
public __gc class FileOpenException : public Exception
{
};
public __gc class FileWriteException : public Exception
{
};
try
{
while( condition )
{
}
}
catch ( FileOpenException* foe )
{
}
catch ( FileWriteException* fwd )
{
}
__finally
{
}
Throwing __value objects
Let's assume that for some reason we want a custom exception object that is a
__value
class
or
__value
struct
(not derived from
System::Exception
obviously)
public __value class MyValueClass
{
public:
int u;
};
If we tried to throw an object of this class, we'd get the following compiler
error message [broken into two lines to prevent scrolling]:-
c:\...\ExceptionsDemo.cpp(107): error C2715: 'MyValueClass' :
unable to throw or catch an interior __gc pointer or a value type
So we need to box the
__value
object before throwing it, as shown below :-
void Test()
{
MyValueClass m;
m.u = 66;
throw __box(m);
}
And we can catch it as an exception of type
System::Object
and then unbox it to access the actual exception object.
void TryTest()
{
try
{
Test();
}
catch(Object* m)
{
MyValueClass v = *dynamic_cast<__box MyValueClass*>(m);
Show(v.u);
}
}
__try_cast and System::InvalidCastException
While this is not directly pertaining to a general discussion on managed
exceptions, I thought I'd mention __try_cast
here which
is an additional keyword supported by the managed extensions to C++. The casting
operator works exactly like the dynamic_cast
operator
except that if the cast fails it throws a
System::InvalidCastException
exception.
void TestCast()
{
try
{
Object* obj = new Object();
String* str = __try_cast<String*>(obj);
}
catch(InvalidCastException* ice)
{
Show(ice->Message);
}
}
Conclusion
I hope this article has thrown light on managed exception handling in
VC++.NET for programmers from a native C++ background, who are gradually moving
to .NET and Managed C++. For further information about .NET exception handling I
recommend Tom Archer's and Andrew Whitechapel's
Inside C# (2nd edition)[^] - specifically Chapter 12 (Pages 411 -
448).
History
- August 09th 2003 - First published