Subterranean IL: Exception handling 1

Posted by Simon Cooper on Simple Talk See other posts from Simple Talk or by Simon Cooper
Published on Thu, 13 Jan 2011 12:42:00 GMT Indexed on 2011/01/13 19:57 UTC
Read the original article Hit count: 286

Filed under:

Today, I'll be starting a look at the Structured Exception Handling mechanism within the CLR. Exception handling is quite a complicated business, and, as a result, the rules governing exception handling clauses in IL are quite strict; you need to be careful when writing exception clauses in IL.

Exception handlers

Exception handlers are specified using a .try clause within a method definition.

.try <TryStartLabel> to <TryEndLabel>
<HandlerType>
handler <HandlerStartLabel> to <HandlerEndLabel>
As an example, a basic try/catch block would be specified like so:
TryBlockStart:
// ...
leave.s CatchBlockEnd
TryBlockEnd:

CatchBlockStart:
// at the start of a catch block, the exception thrown is on the stack
callvirt instance string [mscorlib]System.Object::ToString()
call void [mscorlib]System.Console::WriteLine(string)
leave.s CatchBlockEnd

CatchBlockEnd:
// method code continues...

.try TryBlockStart to TryBlockEnd
catch [mscorlib]System.Exception
handler CatchBlockStart to CatchBlockEnd

There are four different types of handler that can be specified:

  1. catch <TypeToken>
    This is the standard exception catch clause; you specify the object type that you want to catch (for example, [mscorlib]System.ArgumentException). Any object can be thrown as an exception, although Microsoft recommend that only classes derived from System.Exception are thrown as exceptions.
  2. filter <FilterLabel>
    A filter block allows you to provide custom logic to determine if a handler block should be run. This functionality is exposed in VB, but not in C#.
  3. finally
    A finally block executes when the try block exits, regardless of whether an exception was thrown or not.
  4. fault
    This is similar to a finally block, but a fault block executes only if an exception was thrown. This is not exposed in VB or C#.

You can specify multiple catch or filter handling blocks in each .try, but fault and finally handlers must have their own .try clause. We'll look into why this is in later posts.

Scoped exception handlers

The .try syntax is quite tricky to use; it requires multiple labels, and you've got to be careful to keep separate the different exception handling sections. However, starting from .NET 2, IL allows you to use scope blocks to specify exception handlers instead. Using this syntax, the example above can be written like so:

.try {
// ...
leave.s EndSEH
}
catch [mscorlib]System.Exception {
callvirt instance string [mscorlib]System.Object::ToString()
call void [mscorlib]System.Console::WriteLine(string)
leave.s EndSEH
}
EndSEH:
// method code continues...

As you can see, this is much easier to write (and read!) than a stand-alone .try clause.

Next time, I'll be looking at some of the restrictions imposed by SEH on control flow, and how the C# compiler generated exception handling clauses.

© Simple Talk or respective owner