Chapter 14

image

Managed Exception Handling

Usually the exception handling model of a programming language is considered the domain of that particular language’s runtime. Under the hood, each language has its own way of detecting exceptions and locating an appropriate exception handler. Some languages perform exception handling completely within the language runtime, whereas others rely on the structured exception handling (SEH) mechanism provided by the operating system—which in your case is Win32 or Win64.

In the world of managed code, exception handling is a fundamental feature of the common language runtime execution engine. The execution engine is fully capable of handling exceptions without regard to language, allowing exceptions to be raised in one language and caught in another. At that, the runtime does not dictate any particular syntax for handling exceptions. The exception mechanism is language neutral in that it is equally efficient for all languages.

No special metadata is captured for exceptions other than the metadata for the exception classes themselves. No association exists between a method of a class and the exceptions that the method might throw. Any method is permitted to throw any exception at any time.

Although we talk about managed exceptions thrown and caught within managed code, a common scenario involves a mix of both managed and unmanaged code. Execution threads routinely traverse managed and unmanaged blocks of code through the use of the common language runtime’s platform invocation mechanism (P/Invoke) and other interoperability mechanisms (see Chapter 18). Consequently, during execution, exceptions can be thrown or caught in either managed code or unmanaged code.

The runtime exception handling mechanism integrates seamlessly with the Win32/Win64 SEH mechanism so that exceptions can be thrown and caught within and between the two exceptionhandling systems.

EH Clause Internal Representation

Managed exception handling tables are located immediately after a method’s IL code, with the beginning of the table aligned on a double word boundary. It would be more accurate to say that “additional sections” are located after the method IL code, but the existing releases of the common language runtime allow only one kind of additional section: the exception handling section.

This additional section begins with the section header, which comes in two varieties (small and fat) and contains two entries, Kind and DataSize. In a small header, DataSize is represented by 1 byte, whereas in a fat header, DataSize is 3 bytes long. A Kind entry can contain the following binary flags:

  • Reserved (0x00): This should not be used.
  • EHTable (0x01): The section contains an exception handling table. This bit must be set.
  • OptILTable (0x02): Not used in the current releases of the runtime. This bit must not be set.
  • FatFormat (0x40): The section header has a fat format—that is, DataSize is represented by 3 bytes.
  • MoreSects (0x80): More sections follow this one.

The section header—padded with 2 bytes if small, which makes one wonder why the small header was introduced at all—is followed by a sequence of EH clauses, which can also have a small or fat format. Each EH clause describes a single triad made up of a guarded block, an exception identification, and an exception handler. The entries of small and fat EH clauses have the same names and meanings but different sizes, as shown in Table 14-1.

Table 14-1. EH Clause Entries

Table14-1.jpg

Branching into or out of guarded blocks and handler blocks is illegal. A guarded block must be entered “through the top”—that is, through the instruction located at TryOffsetand handler blocks are entered only when they are engaged by the exception handling subsystem of the execution engine. To exit guarded and handler blocks, you must use the instruction leave (or leave.s). You might recall that in Chapter 2, this principle was formulated as “leave only by leave.” Another way to leave any block is to throw an exception using the throw or rethrow instruction.

Types of EH Clauses

Exception handling clauses are classified by the algorithm of the handler engagement. Four mutually exclusive EH clause types are available, and because of that, the Flags entry must hold one of the following values:

  • 0x0000: The handler must be engaged if the type of the exception object matches the type identified by the token specified in the ClassToken entry or any of this type’s descendants. Theoretically, any object can be thrown as an exception, but it’s strongly recommended that all exception types be derived from the [mscorlib]System.Exception class. This is because throughout Microsoft .NET Framework classes the construct catch [mscorlib]System.Exception is used in the sense of “catch any exception”—it is an analog of catch(...) in C++. In other words, [mscorlib]System.Exception is presumed to be the ultimate base class for all exceptions. This type of EH clause is called a catch type.
  • 0x0001: A dedicated block of the IL code, called a filter, will process the exception and define whether the handler should be engaged. The offset of the filter block is specified in the FilterOffset entry. Since you cannot specify the filter block length—the EH clause structure contains no entry for it—a positioning limitation is associated with the filter block: the respective handler block must immediately follow the filter block, allowing the length of the filter block to be inferred from the offset of the handler. The filter block must end with the endfilter instruction, described in Chapter 13. At the moment endfilter is executed, the evaluation stack must hold a single int32 value, equal to 1 if the handler is to be engaged and equal to 0 otherwise. This EH clause type is called a filter type. Branching into or out of the filter block is illegal. Falling through into the filter block is also illegal.
  • 0x0002: The handler will be engaged upon exiting the guarded block, whether or not an exception has occurred. The EH clause entry ClassToken/FilterOffset is ignored. This EH clause type is called a finally type. The finally handlers are not meant to process an exception but rather to perform any cleanup that might be needed when leaving the guarded block. The finally handlers must end with the endfinally instruction. If no exception has occurred within the guarded block, the finally handler is executed at the moment of leaving that block. If an exception has been thrown within the guarded block, the finally handler is executed after any preceding handler (of a nested guarded block) is executed or, if no preceding handler was engaged, before any following handler is executed. If no catch or filter handlers are engaged—that is, the exception is uncaught—the finally handler is engaged when the CLR registers the uncaught exception, right before the application execution is aborted.

    Figure 14-1 illustrates this process. If an exception of type A is thrown within the innermost guarded block, it is caught and processed by the first handler (catch A), and the finally handler is engaged after the first handler executes the leave instruction. If an exception of type B is thrown, it is caught by the third handler (catch B); this fact is registered by the runtime, and the finally handler is executed before the third handler. If no exception is thrown within the guarded block, the finally handler is engaged when the guarded block executes the leave instruction.

    9781430267614_Fig14-01.jpg

    Figure 14-1. Engagement of the finally exception handler

  • 0x0004: The handler will be engaged if any exception occurs. This type of EH clause is called a fault type. A fault handler is similar to a finally handler in all aspects except one: the fault handler is not engaged if no exception has been thrown within the guarded block and everything is nice and quiet. The fault handler must also end with the endfinally instruction, which for this specific purpose has been given the synonym endfault.

Label Form of EH Clause Declaration

The most generic form of ILAsm notation of an EH clause is

.try<label> to<label> <EH_type_specific> handler<label> to<label>

where <EH_type_specific> ::=

catch<class_ref> |
filter<label>|
finally |
fault

Take alook at this example:

BeginTry:
   ...
   leave KeepGoing
BeginHandler:
   ...
   leave KeepGoing
KeepGoing:
   ...
   ret
   .try BeginTry to BeginHandler catch[mscorlib]System.Exception
      handler BeginHandler to KeepGoing

In the final lines of the example, the code .try <label> to <label> defines the guarded block, and handler <label> to <label> defines the handler block. In both cases, the second <label> is exclusive, pointing at the first instruction after the respective block. ILAsm imposes a limitation on the positioning of the EH clause declaration directives: all labels used in the directives must have already been defined. Thus, the best place for EH clause declarations in the label form is at the end of the method scope.

In the case just presented, the handler block immediately follows the guarded block, but you could put the handler block anywhere within the method, provided it does not overlap with the guarded block or other handlers:

   ...
   br AfterHandler//  Can't enter the handler block on our own
BeginHandler:
   ...
   leave KeepGoing
AfterHandler:
   ...
BeginTry:
   ...
   leave KeepGoing
KeepGoing:
   ...
   ret
   .try BeginTry to KeepGoing catch[mscorlib]System.Exception
      handler BeginHandler to AfterHandler

A single guarded block can have several catch or filter handlers:

   ...
   br AfterHandler2//  Can't enter the handler block(s) on our own
BeginHandler1:
   ...
   leave KeepGoing
AfterHandler1:
   ...
BeginHandler2:
   ...
   leave KeepGoing
AfterHandler2:
   ...
BeginTry:
   ...
   leave KeepGoing
KeepGoing:
   ...
   ret
   .try BeginTry to KeepGoing
      catch[mscorlib]System.NullReferenceException
         handler BeginHandler1 to AfterHandler1
   .try BeginTry to KeepGoing catch[mscorlib]System.Exception
      handler BeginHandler2 to AfterHandler2

In the case of multiple handlers—catch or filter but not finally or fault, as explained nextthe guarded block declaration need not be repeated.

.try BeginTry to KeepGoing
   catch[mscorlib]System.NullReferenceException
      handler BeginHandler1 to AfterHandler1
   catch[mscorlib]System.Exception
      handler BeginHandler2 to AfterHandler2

The lexical order of handlers belonging to the same guarded block is the order in which the IL assembler emits the EH clauses and is the same order in which the execution engine of the runtime processes these clauses. You must be careful about ordering the handlers. For instance, if you swap the handlers in the preceding example, the handler for [mscorlib]System.Exception will always be executed, and the handler for [mscorlib]System.NullReferenceException will never be executed. This is because all standard exceptions are (and user-defined should be) derived, eventually, from [mscorlib]System.Exception, and hence all exceptions are caught by the first handler, leaving the other handlers unemployed.

The finally and fault handlers cannot peacefully coexist with other handlers, so if a guarded block has a finally or fault handler, it cannot have anything else. To combine a finally or fault handler with other handlers, you need to nest the guarded and handler blocks within other guarded blocks, as shown in Figure 14-1, so that each finally or fault handler has its own personal guarded block.

Scope Form of EH Clause Declaration

The label form of the EH clause declaration is universal, ubiquitous, and close to the actual representation of the EH clauses in the EH table. The only quality the label form lacks is convenience. In view of that, ILAsm offers an alternative form of EH clause description: a scope form. You’ve already encountered the scope form in Chapter 2, which discussed protecting the code against possible surprises in the unmanaged code being invoked. Just to remind you, here’s what the protected part of the method (from the sample file Simple2.il on the Apress web site) looks like:

   ...
   .try{
        // Guarded block begins
        call string[mscorlib]System.Console::ReadLine()
        //pop
        //ldnull
        ldstr"%d"
        ldsflda int32Odd.or.Even::val
        call vararg int32sscanf(string, string,..., int32*)
        stloc.0
        leave.s DidntBlowUp
        // Guarded block ends
   }
   catch[mscorlib]System.Exception
   {// Exception handler begins
        pop
        ldstr"KABOOM!"
        call void[mscorlib]System.Console::WriteLine(string)
        leave.s Return
   }// Exception handler ends
DidntBlowUp:
   ...

The scope form can be used only for a limited subset of all possible EH clause configurations: the handler blocks must immediately follow the previous handler block or the guarded block. If the EH clause configuration is different, you must resort to the label form or a mixed form in which the guarded block is scoped but the catch handler is specified by IL offsets, or vice versa.

   ...
   br AfterHandler
HandlerBegins:
   // The exception handler code
   ...
   leave KeepGoing
AfterHandler:
   ...
   .try{
      // Guarded code
      ...
      leave KeepGoing
   }
   catch[mscorlib]System.Exception
      handler HandlerBegins to AfterHandler
   ...
KeepGoing:
   ...

The IL disassembler by default outputs the EH clauses in the scope form—at least those clauses that can be represented in this form. However, there is an  option to suppress the scope form and output all EH clauses in their label form (command-line option /RAW). But let’s suppose for the sake of convenience that you can shape the code in such a way that the contiguity condition is satisfied, allowing you to use the scope form. A single guarded block with multiple handlers in scope form will look like this:

.try{
   // Guarded code
   ...
   leave KeepGoing
}
catch[mscorlib]System.NullReferenceException {
   // The exception handler #1 code
   ...
   leave KeepGoing
}
catch[mscorlib]System.Exception {
   // The exception handler #2 code
   ...
   leave KeepGoing
}
   ...
KeepGoing:
   ...

Much more readable, isn’t it? The nested EH configuration shown earlier in Figure 14-1 is easily understandable when written in scope form:

.try{
   .try{
      .try{
         // Guarded code
         ...
         leave L1
      }
      catch A {
         // This code works when exception A is thrown
         ...
         leave L2
      }
   } // No need for leave here!
   finally{
      // This code works in any case
      ...
      endfinally
   }
}// No need forleavehere either!
catch B {
   //  This code works when exception B is thrown in guarded code
   ...
   leave L3
}
L1:
   ...
L2:
   ...
L3:
   ...

The filter EH clauses in scope form are subject to the same limitation: the handler block must immediately follow the guarded block. But in a filter clause the handler block includes first the filter block and then, immediately following it, the actual handler, so the scope form of a filter clause looks like this:

.try{
   // Guarded code
   ...
   leave KeepGoing
}
filter{
   // Here we decide whether we should invoke the actual handler
   ...
   ldc.i4.1// OK, let's invoke the handler
   endfilter
} {
   // Actual handler code
   ...
   leave KeepGoing
}
KeepGoing:
   ...

And, of course, you easily switch between scope form and label form within a single EH clause declaration. The general ILAsm syntax for an EH clause declaration is as follows:

<EH_clause> ::= .try<guarded_block>
                     <EH_type_specific> <handler_block>
Where
<guarded_block> ::= <label> to<label>| <scope>
<EH_type_specific> ::= catch<class_ref> |
         filter<label> | filter<scope> |
         finally|
         fault
<handler_block> ::= handler<label> to<label> | <scope>

The nonterminals <label> and <class_ref> must be familiar by now, and the meaning of <scope> is obvious: “code enclosed in curly braces.”

Processing the Exceptions

The execution engine of the CLR processes an exception in two passes. The first pass determines which, if any, of the managed handlers will process the exception. Starting at the top of the EH table for the current method frame, the execution engine compares the address where the exception occurred to the TryOffset and TryLength entries of each EH clause. If it finds that the exception happened in a guarded block, the execution engine checks to see whether the handler specified in this clause will process the exception. (The “rules of engagement” for catch and filter handlers were discussed in previous sections.) If this particular handler can’t be engaged—for example, the wrong type of exception has been thrown—the execution engine continues traversing the EH table in search of other clauses that have guarded blocks covering the exception locus. The finally and fault handlers are ignored during the first pass.

If none of the clauses in the EH table for the current method are suited to handling the exception, the execution engine steps up the call stack and starts checking the exception against EH tables of the method that called the method where the exception occurred. In these checks, the call site address serves as the exception locus. This process continues from method frame to method frame up the call stack, until the execution engine finds a handler to be engaged or until it exhausts the call stack. The latter case is the end of the story: the execution engine cannot continue with an unhandled exception on its conscience, and the runtime executes all finally and fault handlers and then either aborts the application execution or offers the user a choice between aborting the execution and invoking the debugger, depending on the runtime configuration.

If a suitable handler is found, the execution engine swings into the second pass. The execution engine again walks the EH tables it worked with during the first pass and invokes all relevant finally and fault handlers. Each of these handlers ends with the endfinally instruction (or endfault, its synonym), signaling the execution engine that the handler has finished and that it can proceed with browsing the EH tables. Once the execution engine reaches the catch or filter handler it found on its first pass, it engages the actual handler.

What happens to the method’s evaluation stack? When a guarded block is exited in any way, the evaluation stack is discarded. If the guarded block is exited peacefully, without raising an exception, the leave instruction discards the stack; otherwise, the evaluation stack is discarded the moment the exception is thrown.

During the first pass, the execution engine puts the exception object on the evaluation stack every time it invokes a filter block. The filter block pops the exception object from the stack and analyzes it, deciding whether this is a job for its actual handler block. The decision, in the form of int32 having the value 1 or 0 (engage the handler or don’t, respectively), is the only thing that must be on the evaluation stack when the endfilter instruction is reached; otherwise, the IL verification will fail. The endfilter instruction takes this value from the stack and passes it to the execution engine.

During the second pass, the finally and fault handlers are invoked with an empty evaluation stack. These handlers do nothing about the exception itself and work only with method arguments and local variables, so the execution engine doesn’t bother providing the exception object. If anything is left on the evaluation stack by the time the endfinally (or endfault) instruction is reached, it is discarded by endfinally (or endfault).

When the actual handler is invoked, the execution engine puts the exception object on the evaluation stack. The handler pops this object from the stack and handles it to the best of its abilities. When the handler is exited by using the leave instruction, the evaluation stack is discarded.

Table 14-2 summarizes the stack evolutions.

Table 14-2. Changes in the Evaluation Stack

When the Block

Is Entered, the Stack…

Is Exited, the Stack…

try

Must be empty

Is discarded

filter

Holds the exception object

Must hold a single int32 value, equal to 1 or 0, consumed by endfilter

handler

Holds the exception object

Is discarded

finally, fault

Is empty

Is discarded

Two IL instructions are used for raising an exception explicitly: throw and rethrow. The throw instruction takes the exception object (ObjectRef) from the stack and raises the exception. This instruction can be used anywhere, within or outside any EH block.

The rethrow instruction can be used within catch handlers only (not within the filter block), and it does not work with the evaluation stack. This instruction signals the execution engine that the handler that was supposed to take care of the caught exception has for some reason changed its mind and that the exception should therefore be offered to the higher-level EH clauses. The only blocks where the words “caught exception” mean something are the catch handler block and the filter block, but invoking rethrow within a filter block, though theoretically possible, is illegal. It is legal to throw the caught exception from the filter block, but it doesn’t make much sense to do so: the effect is the same as if the filter simply refused to handle the exception, by loading 0 on the stack and invoking endfilter.

Rethrowing an exception is not the same as throwing the caught exception, which you have on the evaluation stack upon entering a catch handler. The rethrow instruction preserves the call stack trace of the original exception so that the exception can be tracked down to its point of origin. The throw instruction starts the call stack trace anew, giving you no way to determine where the original exception came from.

Exception Types

Chapter 13 mentioned some of the exception types that can be thrown during the execution of IL instructions. Earlier chapters mentioned some of the exceptions thrown by the loader and the JIT compiler. Now it’s time to review all these exceptions in an orderly manner.

All managed exceptions defined in the .NET Framework classes are descendants of the mscorlib]System.Exception class. This base exception type, however, is never thrown by the common language runtime. In the following sections, I’ve listed the exceptions the runtime does throw, classifying them by major runtime subsystems. Enjoying the monotonous repetition no more than you do, I’ve omitted the [mscorlib]System. part of the names, common to all exception types. As you can see, many of the exception type names are self-explanatory.

Loader Exceptions

The loader represents the first line of defense against erroneous applications, and the exceptions it throws are related to the file presence and integrity:

  • AppDomainUnloadedException: The code tries to access an appdomain which has been unloaded.
  • CannotUnloadAppDomainException: The code tries to unload an appdomain and fails to do that.
  • BadImageFormatException: Corrupt file headers or segments that belong in read-only sections (such as the runtime header, metadata, and IL code) are located in writable sections of the PE file. Or you may be trying to load a 32-bit specific image into a 64-bit process or vice versa.
  • ArgumentException: An argument is wrong in some respect (for example, a string is empty when it should contain a valid path). This exception is also thrown by the JIT compiler and the interoperability services.
  • Security.Cryptography.CryptographicException: Some error occurred during a cryptographic operation, for example, a file hash check.
  • FileLoadException: Failure to load a specified file for some reason other than those covered by BadImageFormatException. For example, the referenced file is not where it is supposed to be.
  • MissingFieldException: The code tries to dynamically access (via the Reflection mechanism) a non-existent field of a class.
  • MissingMethodException: Same story with a non-existent method.
  • TypeLoadException: This exception, which is most frequently thrown by the loader, indicates that the type metadata is illegal.
  • UnauthorizedAccessException: A user application is attempting to directly manipulate the system assembly Mscorlib.dll.
  • OutOfMemoryException: This exception, which is also thrown by the execution engine, indicates memory allocation failure.

JIT Compiler Exceptions

The JIT compiler throws only two exceptions. The second one can be thrown only when the code is not fully trusted (for example, comes from the Internet).

  • InvalidProgramException: This exception, which is also thrown by the execution engine, indicates an error in IL code.
  • VerificationException: This exception, which is also thrown by the execution engine, indicates that IL code verification has failed.

Execution Engine Exceptions

The execution engine throws a wide variety of exceptions, most of them related to the operations on the evaluation stack. A few exceptions are thrown by the thread control subsystem of the engine.

  • ArithmeticException: Base class for DivideByZeroException, OverflowException, and NotFiniteNumberException.
  • ArgumentOutOfRangeException: An argument has a value outside predefined range.
  • ArrayTypeMismatchException: The code tries to store an element of wrong type in an array, for example, to assign a string to an element of integer array. This exception is also thrown by the interoperability services.
  • DivideByZeroException: Well, this one is pretty obvious.
  • DuplicateWaitObjectException: The same object is included more than once in an array of synchronization objects.
  • ExecutionEngineException: This is the generic exception, indicating that some sequence of IL instructions has brought the execution engine into a state of complete perplexity—as a rule, by corrupting the memory. Verifiable code cannot corrupt the memory and hence does not raise exceptions of this type.
  • FieldAccessException: This exception indicates, for example, an attempt to load from or store to a private field of another class.
  • FormatException: An attempt is made to parse a string formatted in an unexpected way. For example, an attempt is made to parse “FOO” as an integer.
  • IndexOutOfRangeException: An attempt to access a non-existent element of an array, for example, the seventh element of an array containing six elements.
  • InvalidCastException: An object reference is cast to a class A, and this reference is not of class A, or of a class derived from A, or of a class implementing the interface A.
  • InvalidOperationException: This exception is thrown when a method call fails for reasons other than invalid call arguments. For example, if the code tries to acquire the address of a field (ldflda) of a class outside current application domain.
  • MethodAccessException: This exception indicates an attempt to call a method to which the caller does not have access, for example, a private method of another class.
  • NotFiniteNumberException: This exception indicates that a floating-point variable unexpectedly acquired the value of NaN, positive infinity, or negative infinity.
  • NotSupportedException: This exception is thrown when a called method is not supported, or when the combination of call arguments forms an unsupported case.
  • NullReferenceException: This exception indicates an attempt to dereference a null pointer (a managed or unmanaged pointer or an object reference).
  • OverflowException:This exception is thrown when a checked conversion fails because the target data type is too small.
  • RankException: This exception is thrown when a method specific to an array is being called on a vector instance.
  • RemotingException: A general exception indicating some failure during remoting.
  • Security.SecurityException: The code tries to access a resource (for example, call a method) it has no permissions to access.
  • StackOverflowException: This one is also pretty obvious. It usually indicates an infinite recursion or too-deep recursion. It may also indicate an attempt to acquire too much of local memory (localloc).  Note: In CLR 2.0 and later, this exception cannot be caught by the managed exception handling system.
  • Threading.SynchronizationLockException: This exception is thrown when an application tries to manipulate or release a lock it has not acquired, for example, by calling the Wait, Pulse, or Exit method before calling the Enter method of the [mscorlib]System.Threading.Monitor class.
  • Threading.ThreadAbortException: This exception is thrown when the Abort method is called on a thread instance. It is an interesting exception, because, while it can be caught by the thread code, it is automatically rethrown at the end of the catch handler block. The CLR executes all finally handler blocks of the affected thread and destroys the thread (that is, if one of those finally handler blocks doesn’t call the ResetAbort method thus cancelling the abort).
  • Threading.ThreadInterruptedException: This exception indicates that a thread is interrupted (method Interrupt is called on its instance) while in a waiting state.
  • Threading.ThreadStateException: This exception is thrown when some method is called on the thread instance when this thread is in “inopportune” state. For example, when the Start method is called on an already running thread.
  • Threading.ThreadStopException: An internal (and hence undocumented) exception thrown when one thread stopped another running thread. Since this exception type was non-public, no user code could catch or handle it. The only chance to see this exception reported was to run a multithreaded application under debugger in Visual Studio 2003. This exception type was removed in v2.0.
  • TypeInitializationException: This exception is thrown when a type—a class or a value type—failed to initialize.

Interoperability Services Exceptions

The following exceptions are thrown by the interoperability services of the common language runtime, which are responsible for managed and unmanaged code interoperation:

  • DllNotFoundException: This exception is thrown when an unmanaged DLL specified as a location of the unmanaged method being called cannot be found.
  • EntryPointNotFoundException: This exception is thrown when the CLR cannot find the entry point method of an EXE assembly or when a P/Invoked unmanaged method cannot be found in specified unmanaged DLL.
  • InvalidComObjectException: This exception is thrown at attempt to use an invalid COM object, to use the _ComObject type directly without having a class factory.
  • Runtime.InteropServices.InvalidOleVariantTypeException: This exception is thrown by the marshaler (see Chapter 18 for details) when a COM variant type cannot be marshaled to managed code.
  • MarshalDirectiveException: This exception is thrown when data cannot be marshaled between managed and unmanaged code in the specified way.
  • Runtime.InteropServices.SafeArrayRankMismatchException: This exception is thrown when the rank (cDims) of an incoming SAFEARRAY differs from the rank specified in the managed signature.
  • Runtime.InteropServices.SafeArrayTypeMismatchException: This exception is thrown when the type (Features) of an incoming SAFEARRAY differs from the type specified in the managed signature.
  • Runtime.InteropServices.COMException: This is a generic exception for COM interoperation. It is thrown when the HRESULT value returned by invoked COM method cannot be classified as a well-known value.
  • Runtime.InteropServices.SEHException: This is the generic managed exception type for unmanaged exceptions.

Subclassing the Exceptions

In addition to the plethora of exception types already defined in the .NET Framework classes, you can always devise your own types tailored to your needs. The best way to do this is to derive your exception types from the “standard” types listed in the preceding sections.

The following exception types are sealed and can’t be subclassed. Again, I’ve omitted the [mscorlib]System. portion of the names.

  • InvalidProgramException
  • TypeInitializationException
  • Threading.ThreadAbortException
  • StackOverflowException

image Caution  As mentioned earlier, I must warn you against devising your own exception types not derived from [mscorlib]System.Exception or some other exception type of the .NET Framework classes.

Unmanaged Exception Mapping

When an unmanaged exception occurs within a native code segment, the execution engine maps it to a managed exception that is thrown in its stead. The different types of unmanaged exceptions, identified by their status code, are mapped to the managed exceptions as described in Table 14-3.

Table 14-3. Mapping Between the Managed and Unmanaged Exceptions

Unmanaged Exception Status Code

Mapped to Managed Exception

STATUS_FLOAT_INEXACT_RESULT

ArithmeticException

STATUS_FLOAT_INVALID_OPERATION

ArithmeticException

STATUS_FLOAT_STACK_CHECK

ArithmeticException

STATUS_FLOAT_UNDERFLOW

ArithmeticException

STATUS_FLOAT_OVERFLOW

OverflowException

STATUS_INTEGER_OVERFLOW

OverflowException

STATUS_FLOAT_DIVIDE_BY_ZERO

DivideByZeroException

STATUS_INTEGER_DIVIDE_BY_ZERO

DivideByZeroException

STATUS_FLOAT_DENORMAL_OPERAND

FormatException

STATUS_ACCESS_VIOLATION

NullReferenceException

STATUS_ARRAY_BOUNDS_EXCEEDED

IndexOutOfRangeException

STATUS_NO_MEMORY

OutOfMemoryException

STATUS_STACK_OVERFLOW

StackOverflowException

All other status codes

Runtime.InteropServices.SEHException

Summary of EH Clause Structuring Rules

The rules for structuring EH clauses within a method are neither numerous nor overly complex.

All the blocks—try, filter, handler, finally, and fault—of each EH clause must be fully contained within the method code. No block can protrude from the method.

The guarded blocks and the handler blocks belonging to the same EH clause or different EH clauses can’t partially overlap. A block either is fully contained within another block or is completely outside it. If one guarded block (A) is contained within another guarded block (B) but is not equal to it, all handlers assigned to A must also be fully contained within B.

A handler block of an EH clause can’t be contained within a guarded block of the same clause, and vice versa. And a handler block can’t be contained in another handler block that is assigned to the same guarded block.

A filter block can’t contain any guarded blocks or handler blocks.

All blocks must start and end on instruction boundaries—that is, at offsets corresponding to the first byte of an instruction. Prefixed instructions must not be split, meaning that you can’t have constructs such as tail. .try { call ... }.

A guarded block must start at a code point where the evaluation stack is empty.

The same handler block can’t be associated with different guarded blocks:

.try Label1 to Label2 catch A handler Label3 to Label4
.try Label4 to Label5 catch B handler Label3 to Label4  // Illegal!

If the EH clause is a filter type, the filter’s actual handler must immediately follow the filter block. Since the filter block must end with the endfilter instruction, this rule can be formulated as “the actual handler starts with the instruction after endfilter.”

If a guarded block has a finally or fault handler, the same block can have no other handler. If you need other handlers, you must declare another guarded block, encompassing the original guarded block and the handler:

.try{
   .try{
      .try{
         //  Code that needs finally, catch, and fault handlers
         ...
         leave KeepGoing
      }
      finally{
         ...
         endfinally
      }
   }
   catch[mscorlib]System.StackOverflowException
   {
      ...
      leave KeepGoing
   }
}
fault{
   ...
   endfault
}
KeepGoing:
   ...
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.95.36