Structured Exception Handling


Every program encounters errors during its run time. The program tries to do something—open a file or create an object, for example—and the operation fails for one reason or another. How does your program find out whether an operation succeeded or failed, and how do you write code to handle the latter case?

Every program needs to handle errors that occur at run time.

The classic approach employed by a failed function is to return a special case value that indicated that failure, say, Nothing (or NULL in C++). This approach had three drawbacks. First, the programmer had to write code that checked the function’s return value, and this often didn’t happen in the time crunch that colors modern software development. Like seat belts or birth control, error-indicating return values only work if you use them. Errors didn’t get trapped at their source but instead got propagated to higher levels of the program. There they were much more difficult to unravel and sometimes got masked until after a program shipped. Second, even if you were paying attention to it, the value of the error return code varied widely from one function to another, increasing the potential for programming mistakes. CreateWindow, for example, indicates a failure by returning NULL, CreateFile returns -1 , and in 16-bit Windows, LoadLibrary returned any value less than 32. To make things even more chaotic, all COM-related functions return 0 as a success code and a nonzero value to indicate different types of failures. Third, a function could return only a single value to its caller, which didn’t give a debugger (human or machine) very much information to work with in trying to understand and fix the error.

Returning a special case value to indicate the failure of a function doesn’t work well.

Different languages tried other approaches to handling run-time errors. Visual Basic used the On Error GoTo mechanism, which was and is a god- awful kludge. GoTo has no place in modern software; it hasn’t for at least a decade and maybe more. C++ and Java used a better mechanism, called structured exception handling (SEH), which uses an object to carry information about a failure and a handler code block to deal with that object. Unfortunately, like most features of any pre–common language runtime language, structured exception handling only worked within that particular language. COM tried to provide rich, cross-language exception handling through the ISupportErrorInfo and IErrorInfo interfaces, but this approach was difficult to program and you were never sure whether your counterpart was following the same rules you were.

No other technique works well across languages either.

The .NET common language runtime provides structured exception handling, similar to that in C++ or Java, as a fundamental feature available to all languages. This architecture solves many of the problems that have dogged error handling in the past. An unhandled exception will shut down your application, so you can’t ignore one during development. A function that is reporting a failure places its descriptive information in a .NET object, so it can contain any amount of information you’d like to report. Since the infrastructure is built into the runtime, you have to write very little code to take advantage of it. And as with all runtime functionality, .NET structured exception handling works well across all languages.

.NET provides structured exception handling as a fundamental feature available in and between all languages.

I’ve written a sample program that demonstrates some of the structured exception handling features in the common language runtime. Figure 2-26 shows a picture of it. You can download the code from the book’s Web site and work along with me.


Figure 2-26: Sample program demonstrating structured exception handling.

A client program about to perform an operation that it thinks might fail sets up an exception handler block in its code, using the keywords Try and Catch, as shown in the Visual Basic .NET code in Listing 2-15. The exact syntax of structured exception handling varies from one language to another, but all the ones I’ve seen so far are pretty close to this.

A client program uses a Try-Catch block to specify its exception handling code.

Listing 2-15: Client application code showing structured exception handling.

start example
Protected Sub btnHandled_Click(ByVal sender As Object, _ ByVal e As System.EventArgs) ’ Entering this block of code writes an exception handler onto ’ the stack. Try ’ Perform an operation that we know will cause an exception. Dim foo As System.IO.FileStream foo = System.IO.File.Open("Non-existent file", IO.FileMode.Open) ’ When an exception is thrown at a lower level of ’ code, this handler block catches it. Catch x As System.Exception ’ Perform whatever cleanup we want to do in response ’ to the exception that we caught. MessageBox.Show(x.Message) End Try End Sub 
end example

When program execution enters the Try block, the common language runtime writes an exception handler to the stack, as shown in Figure 2-27. When a called function lower down on the stack throws an exception, as described in the next paragraph, the runtime exception-handling mechanism starts examining the stack upward until it finds an exception handler. The stack is then unwound (all objects on it discarded), and control transfers to the exception handler. An exception can come from any depth in the call stack. In the sample program, I deliberately open a file that I know does not exist. The system method File.Open throws an exception, and my client catches it and displays information to the user about what has happened.

click to expand
Figure 2-27: Structured exception handling diagram.

Any code that wants to can throw an exception. The common language runtime uses SEH for all of its error reporting, as shown in the previous example. For consistency, you therefore probably want to use SEH to signal errors from one part of your application to another. A piece of code that wants to throw an exception creates a new object of type System.Exception. You set the properties of this object to whatever you want them to be to describe the exception situation to any interested catchers. The common language runtime automatically includes a stack trace so that the exception handler code can tell exactly where the exception originated. Then you throw the exception using the keyword Throw, as shown in the code in Listing 2-16. This call tells the system to start examining the stack for handlers. The exception handler can live any number of levels above the exception thrower in the call stack.

A piece of code that wants to throw an exception creates a System.Exception object, fills out its fields, and calls the system function Throw.

Listing 2-16: Throwing an exception in SEH.

start example
Public Function BottomFunction() As String ’ Create a new Exception object, setting its "Message" property, ’ which can only be done in the constructor. Dim MyException _ As New Exception("Exception thrown by BottomFunction") ’ Set the new Exception’s Source property, which can be ’ done anywhere. MyException.Source = _ "Introducing Microsoft .NET Chapter 2 ExceptionComponent" ’ Throw the exception. Throw MyException End Function
end example

When the common language runtime transfers control to an exception handler, the program stack between the thrower and the handler is discarded, as shown previously in Figure 2-27. Any objects or object references that existed on that stack become garbage. Because of the .NET automatic garbage collection, you don’t have to worry about objects being leaked away, which was a big concern when using C++ native exception handling. However, having the objects discarded in this manner means that you don’t get a chance to call the Dispose methods of any that needed deterministic finalization. Their finalizers will be called eventually at the next garbage collection, but that might not be soon enough. You can handle this situation with a Try- Finally handler, as shown in Listing 2-17. Code in a Finally block is executed as the stack is unwound, so you can put your cleanup code there. You can use both a Catch and a Finally block on the same Try if you want to.

You can enforce cleanup from an exception using a Try-Finally block.

Listing 2-17: Finally handler in structured error handling.

start example
Public Function MiddleFunction() As String ’ Entering this block causes a handler to be written onto the stack. Try BottomFunction() ’ The code in this Finally handler is executed whenever ’ execution leaves the Try block for any reason. We care most ’ about the case in which BottomFunction throws an exception ’ and the stack is unwound. Without the Finally handler, we’d ’ have no chance to clean up from that exception. Finally MessageBox.Show("Finally handler in MiddleFunction") End Try End Function
end example

SEH becomes even more powerful if throwers throw different types of exceptions to indicate different types of program failure. You do this by deriving your own class from the generic base class System.Exception. You can add any additional methods or properties to your exception class that you think would explain the situation to any potential catchers. In the example shown at the start of this section, when I attempted to open the nonexistent file, the system threw an exception of type FileNotFoundException, which contained the name of the file that it couldn’t find. Even if you don’t add anything else, the mere presence of a particular type of exception will indicate what type of failure has taken place. I wrote the handler shown in Listing 2-15 to catch any type of exception. If I wanted the handler to catch only exceptions of the type FileNotFoundException, I would change Catch x As System.Exception to Catch x As System.IO.FileNotFoundException. The common language runtime, when examining the stack, matches the type of exception thrown to the type specified in the Catch block, transferring control only if the type thrown matches exactly or is derived from the specified Catch type. A Try block can have any number of Catch handlers attached to it. The common language runtime will search them in the order in which they appear, so you want to put the most specific ones first.

You can throw and catch many different types of exceptions.

start sidebar
Tips from the Trenches

My customers report that they get their best use of exception handling when their code in any given place catches only the types of exceptions that it knows how to handle. Exception handling code shouldn’t merely absorb all System.Exception objects as this sample does. However, they like putting a handler for all exceptions at the very top of their program, enclosing the Application.Run call in their Main function. An exception reaching that handler means that it somehow escaped all the lower handlers and would otherwise cause the program to crash. In this handler, they log the exception to a file or send e-mail, save any open work as best they can, and terminate peacefully. The stack trace in the exception shows exactly where it originated, which makes it much easier to fix.

end sidebar




Introducing Microsoft. NET
Introducing Microsoft .NET (Pro-Developer)
ISBN: 0735619182
EAN: 2147483647
Year: 2003
Pages: 110

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net