Basic Concepts in Using Managed Exceptions

In this topic, the basic concepts for exception handling in managed applications are discussed:

  • Throwing Exceptions Under /clr

  • Try/Catch Blocks for CLR Extensions

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

Throwing Exceptions Under /clr

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;
}

Try/Catch Blocks for CLR Extensions

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 );
      }
   }
}

Output

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

Order of Unwinding for C++ Objects

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.

Catching Unmanaged Types

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, one of the Visual C++ exception handling compiler options must be used /EHs or /Eha.

See Also

Concepts

Exception Handling under /clr

safe_cast

Exception Handling in Visual C++