Basic Concepts in Using Managed Exceptions

This topic discusses exception handling in managed applications. That is, an application that is compiled with the /clr compiler option.

If you compile with the /clr option, you can handle CLR exceptions as well as standard C++ exception handling and structured exception handling (SEH). A CLR exception is any exception thrown by a managed type. The System::Exception class provides many useful methods for processing CLR exceptions and is recommended as a base class for user-defined exception classes.

Catching exception types derived from an interface is not supported under /clr. Also, the common language runtime does not permit you to catch stack overflow exceptions; a stack overflow exception will terminate the process.

For more information about differences in exception handling in managed and unmanaged applications, see Differences in Exception Handling Behavior Under Managed Extensions for C++.

The C++ throw expression is extended to throw a handle to a CLR type. The following example creates a custom exception type and then throws an instance of that type:

// clr_exception_handling.cpp
// compile with: /clr /c
ref struct MyStruct: public System::Exception {
public:
   int i;
};

void GlobalFunction() {
   MyStruct^ pMyStruct = gcnew MyStruct;
   throw pMyStruct;
}

A value type must be boxed before being thrown:

// clr_exception_handling_2.cpp
// compile with: /clr /c
value struct MyValueStruct {
   int i;
};

void GlobalFunction() {
   MyValueStruct v = {11};
   throw (MyValueStruct ^)v;
}

The same try/catch block structure can be used for catching both CLR and native exceptions:

// clr_exception_handling_3.cpp
// compile with: /clr
using namespace System;
ref struct MyStruct : public Exception {
public:
   int i;
};

struct CMyClass {
public:
   double d;
};

void GlobalFunction() {
   MyStruct^ pMyStruct = gcnew MyStruct;
   pMyStruct->i = 11;
   throw pMyStruct;
}

void GlobalFunction2() {
   CMyClass c = {2.0};
   throw c;
}

int main() {
   for ( int i = 1; i >= 0; --i ) {
      try {
         if ( i == 1 )
            GlobalFunction2();
         if ( i == 0 )
            GlobalFunction();
      }
      catch ( CMyClass& catchC ) {
         Console::WriteLine( "In 'catch(CMyClass& catchC)'" );
         Console::WriteLine( catchC.d );
      }
      catch ( MyStruct^ catchException ) {
         Console::WriteLine( "In 'catch(MyStruct^ catchException)'" );
         Console::WriteLine( catchException->i );
      }
   }
}

In 'catch(CMyClass& catchC)'
2
In 'catch(MyStruct^ catchException)'
11

Unwinding occurs for any C++ objects with destructors that may be on the run-time stack between the throwing function and the handling function. Because CLR types are allocated on the heap, unwinding does not apply to them.

The order of events for a thrown exception is as follows:

  1. The runtime walks the stack looking for the appropriate catch clause, or in the case of SEH, an except filter for SEH, to catch the exception. Catch clauses are searched first in lexical order, and then dynamically down the call stack.

  2. Once the correct handler is found, the stack is unwound to that point. For each function call on the stack, its local objects are destructed and __finally blocks are executed, from most nested outward.

  3. Once the stack is unwound, the catch clause is executed.

When an unmanaged object type is thrown, it is wrapped with an exception of type System::Runtime.InteropServices::SEHException. When searching for the appropriate catch clause, there are two possibilities.

  • If a native C++ type is encountered, the exception is unwrapped and compared to the type encountered. This comparison allows a native C++ type to be caught in the normal way.

  • However, if a catch clause of type SEHException or any of its base classes is examined first, the clause will intercept the exception. Therefore, you should place all catch clauses that catch native C++ types first before any catch clauses of CLR types.

Note that

catch(Object^)

and

catch(...)

will both catch any thrown type including SEH exceptions.

If an unmanaged type is caught by catch(Object^), it will not destroy the thrown object.

When throwing or catching unmanaged exceptions, we recommend that you use the /EHsc compiler option instead of /EHs or /EHa.

Community Additions

ADD
Show: