Using Windows Structured Exception Handling (SEH)

Windows provides a robust exception and termination handling mechanism called Structured Exception Handling (SEH). Structured exception handling requires support in both the operating system and compilers. Unfortunately, Visual Fortran does not include extensions for SEH support, but you can still take advantage of this powerful tool. By introducing a bit of C code in your application, you can use SEH to meet your exception handling needs.

You will need a C compiler with some form of SEH support. In this discussion, the syntax supported by Microsoft Visual C++ is used. Other compilers may provide SEH support, but use a different syntax.

Structured exception handling includes both exception and termination handling. Both mechanisms will be shown and explained through working examples.

The following topics are discussed:

Custom Handlers for Fortran Console, Fortran QuickWin, and Fortran Standard Graphics Applications

Fortran Console and Fortran QuickWin (and Fortran Standard Graphics) applications have the full benefit of the Fortran default exception and error handling processing facilities. You may, however, want to supplement or replace the default facilities. Two Visual Fortran samples show how you can accomplish this:

Sample Program CSLEXCP2

The following discussion refers to the sample program CSLEXCP2 provided with the Visual Fortran kit in the ...\SAMPLES\EXCEPTIONHANDLING folder.

Suppose you have the problem described earlier of deciding what to do about open files your application has created in the event something unexpected occurs during execution of your application. Sample application CSLEXCP2 shows one way you might approach solving the problem.

Looking at CSLEXCP2.F90, there is a main program CSLEXCP2, and three additional routines, MY_CODE, MY_EXCEPTION_FILTER_CODE, and MY_TERMINATION_HANDLER_CODE. Routine MY_CODE is the main body of Fortran code in the application. It opens (OPEN statement) a data file and sits in a loop prompting for two integers, and writes (WRITE statement) the result of dividing the first by the second integer to the data file.

If the user enters the sentinel value of 99 for variable I (and not 0 for J), the application exits normally and the data file is closed (CLOSE statement) by the Fortran run-time system as a result of the call to the EXIT subroutine, but not deleted. If the user enters a 0 (zero) for variable J, an integer divide by zero exception occurs. This is the code we want to protect with a custom exception handler.

Look at the main program, CSLEXCP2:

 PROGRAM CSLEXCP2

 INTERFACE
     SUBROUTINE MY_CODE_WRAPPER()
     !DEC$ IF DEFINED(_X86_)
        !DEC$ATTRIBUTES C, ALIAS:'_MY_CODE_WRAPPER' :: MY_CODE_WRAPPER
     !DEC$ ELSE
        !DEC$ATTRIBUTES C, ALIAS:'MY_CODE_WRAPPER' :: MY_CODE_WRAPPER
     !DEC$ ENDIF
     END SUBROUTINE
 END INTERFACE

 CALL MY_CODE_WRAPPER()

 END

All CSLEXCP2 does is call a routine named MY_CODE_WRAPPER. MY_CODE_WRAPPER is a C routine that implements the custom handler. To see how this is done, look at file wrapper.c. MY_CODE_WRAPPER wraps a try-except construct around a call to the main body of the Fortran application, MY_CODE. It also wraps a try-finally construct around the try-except construct. If an exception occurs during execution of MY_CODE, the operating system will evaluate the except filter expression to see if this handler wishes to handle the event. The except filter expression is just a call to the Fortran routine MY_EXCEPTION_FILTER_CODE.

If MY_EXCEPTION_FILTER_CODE returns EXCEPTION_EXECUTE_HANDLER, the operating system continues execution in the except handler block, which in this case is empty. Execution would then continue in the __finally block which calls the general termination handler for the application, MY_TERMINATION_HANDLER_CODE. Routine MY_EXCEPTION_FILTER_CODE queries the user to see if the data file should be deleted or not. That is all the basic code needed to handle the exception situation. Routine MY_TERMINATION_HANDLER_CODE just prints a message and calls the EXIT subroutine to exit the application.

Note, if MY_EXCEPTION_FILTER_CODE returns EXCEPTION_CONTINUE_SEARCH, the operating system continues searching its list of exception handlers and finds the default Fortran run-time exception filter. The code in the __finally block would never be executed because the default run-time handler will exit the process as part of its default handling of the exception. You can experiment with this when you reply to the handler query "Enter A to exercise the termination handler code path,...or B to pass the exception to the default handler."

There are a couple of points to notice in this example. First, remember that this is a console application and its infrastructure basically looks like a C program. The entry point for the application is mainCRTStartup in the C run-time system. Routine mainCRTStartup() initializes the C run-time system and calls the Fortran run-time system main() routine. The Fortran run-time main() routine initializes the Fortran run-time system and calls entry point _MAIN__ in the Fortran code.

If you build the sample and request a listing with machine code from the Fortran compiler, you can see the bit of prolog code that the Fortran compiler generates which provides further initialization before actually executing your Fortran code. Here is an excerpt of that listing:

				.CODE
				PUBLIC	_MAIN__
	     0000	_MAIN__ PROC
55           0000		push	ebp
EC8B         0001		mov	ebp, esp
24EC83       0003		sub	esp, 36
53           0006		push	ebx
000000E8     0007		call	_for_check_flawed_pentium@0
01
04EC83       000C		sub	esp, 4
0004058D     000F		lea	eax, dword ptr .literal$+4
0002
50           0015		push	eax
000000E8     0016		call	_for_set_fpe_@4
01
04C483       001B		add	esp, 4
04EC83       001E		sub	esp, 4
00001D8D     0021		lea	ebx, dword ptr .literal$
0002
53           0027		push	ebx
000000E8     0028		call	_for_set_reentrancy@4
01
04C483       002D		add	esp, 4
000000E8     0030		call	_MY_CODE_WRAPPER

Notice the calls to the Fortran run-time routines _for_check_flawed_pentium@0, _for_set_fpe_@4, and _for_set_reentrancy@4 that occur prior to the call to _MY_CODE_WRAPPER. This code was compiled with /fpe:0 selected, so you can see the call to _for_set_fpe_@4 to set up the non-default floating-point behavior on ia32 systems. This call would not be there with the default /fpe:3 option. The sample does not do any floating-point calculation (the /fpe:0 option was chosen just to illustrate the point). Likewise, if you deselect the check for flawed Pentium run-time option, the call to _for_check_flawed_pentium@0 would not be generated.

This prolog code is only executed as a result of the underlying structure of Fortran Console, Fortran QuickWin, and Fortran Standard Graphics applications. It would not be executed in a Fortran Windows application or Fortran DLL because there would be no Fortran run-time system main() routine present to call MAIN__.

Most of the work of handling the exception is really done in the course of executing the filter expression. The same is true of the Fortran default handler. So, if you allow an exception to be passed to the default run-time system handler, you will not get control again because the default handler will exit the process from within its exception filter in almost all cases. This short circuits any exception unwinding so your termination handler (the __finally construct) will never execute.

The default run-time exception handler plays a role in how an application behaves when exceptions occur while running under the debugger. Normally, the occurrence of an exception while debugging would cause the default handler to do its normal activities except it would not exit the process. Instead, it returns EXCEPTION_CONTINUE_SEARCH so the operating system will give the debugger a second chance at handling the exception and the debugger will stop in your code with the cursor pointing where the unhandled exception happened. If you are more interested in seeing where the exception happened than you are to see your termination handler execute, you can return EXCEPTION_CONTINUE_SEARCH from your exception filter and let the default run-time system handler get the application stopped in the debugger as usual.

In this (CSLEXCP2) example, no traceback output is produced to show where the integer divide by zero happened. This is normally done by the default handler, but we have overridden that handler. The next example shows that you can generate the traceback output in your custom handler using the TRACEBACKQQ routine.

Sample Program CSLEXCP4

The following discussion refers to the sample program CSLEXCP4 provided with the Visual Fortran kit in the ...\SAMPLES\EXCEPTIONHANDLING folder.

Suppose you want to allow the default handler to handle some exceptions, for example, floating-point underflow with /fpe:0. You would like to have the default handler provide the result fix up to zero for this case. Also, suppose you want to turn an exception into a simple program status code which your code can then act upon. Sample CSLEXCP4 will demonstrate a few techniques to accomplish this.

The premise of sample CSLEXCP4 is that a Fortran subroutine is to be called which may generate floating point exceptions. We would like to detect floating point divide by zero exceptions and take some useful action to shut down the application gracefully. The application is to be compiled /fpe:0 and it is desired to have the default Fortran run-time system handler continue handling floating point underflow exceptions.

Looking at the code in CSLEXCP4.F90, the subroutine to be protected is called DIVIDE. In a real application, the protected subroutine could be any complex sequence you like, but in this sample, the subroutine simply does a divide of two input numbers and prints the result. The main Fortran program does not call DIVIDE directly. It makes the call through a C wrapper function called DIVIDE_WRAPPER.

The DIVIDE_WRAPPER function, in file WRAPPER.C, wraps the call to DIVIDE in a try-except construct. In this simple example, it is assumed that if DIVIDE returns without an exception, then all is well and DIVIDE_WRAPPER sets the rtn_sts variable accordingly. If no exception occurs, the except filter expression is never evaluated and the except block is never executed. Execution continues with the return statement just below the except handler block that returns rtn_sts.

If an exception does occur during execution of DIVIDE, the except filter expression is evaluated by the operating system. The filter expression takes two actions:

  1. It captures the exception code related to the event in a local variable, ecode, for use in the exception handler code block. If the exception handler code block is executed, it will examine the exception code to determine a status value to return in rtn_sts.
  2. It calls a Fortran function called CHECK_EXCEPTION_INFO, passing it the value returned from the Win32 routine GetExceptionInformation.

If function CHECK_EXCEPTION_INFO returns EXCEPTION_EXECUTE_HANDLER, the operating system will execute the except handler code block. The handler block will examine the exception code and set rtn_sts to a value indicating a divide by zero occurred or to a value indicating some other exception occurred. Execution then continues with the return statement that returns the value of rtn_sts.

If function CHECK_EXCEPTION_INFO returns EXCEPTION_CONTINUE_SEARCH, the operating system will continue looking back up the list of exception filters looking for one that wants to handle the exception. In this sample, the next exception filter is the default Fortran run-time system exception filter.

The value passed to CHECK_EXCEPTION_INFO is the address of a data structure which contains pointers to an operating system supplied exception record and context record. CHECK_EXCEPTION_INFO uses this information to determine what happened and how to proceed. If this was a floating-point divide by zero, CHECK_EXCEPTION_INFO calls TRACEBACKQQ to produce a stack trace along with an application specific message. It then sets the return value of the function to EXCEPTION_EXECUTE_HANDLER and returns.

If this was a floating-point underflow exception, CHECK_EXCEPTION_INFO calls TRACEBACKQQ again, only so you can see what is happening in the application output. Then, it sets the return value of the function to EXCEPTION_CONTINUE_SEARCH. When the operating system sees this return value, it evaluates the Fortran run-time default except filter expression, which causes the floating underflow result to be fixed up with zero, and execution will continue in the main program at the point of the exception.

The main program initializes two arrays with a set of three values that will demonstrate each path of execution. A DO loop is traversed for three iterations:

  1. The first trip around the loop shows what happens when there are no exceptions.
  2. The second iteration shows a floating underflow being flushed to zero in the default handler.
  3. The third (final) iteration shows a floating divide by zero being processed. The program takes action to shutdown the application with a simple STOP statement and message.

Custom Handlers For Fortran DLL Applications

There are two aspects of creating custom handlers for Fortran DLL applications:

Containing Errors and Exceptions in Fortran DLL's

If you are building a Fortran DLL and intend to call it from a Visual Basic GUI or a main program written in some other language, you want to be careful that errors and exceptions in the DLL do not crash your main application. Here are a few basic principles to keep in mind if you are building a Fortran DLL:

Enabling Floating-Point Traps in Fortran DLL's

Before you can worry about how you will handle a floating-point trap condition occurring in a DLL, you have to consider the problem of unmasking those traps so they can occur. If you are compiling /fpe:3 and polling the floating-point status word to check for exceptions, you do not have to worry about the problem of unmasking traps. You do not want traps unmasked in that case.

However, if your strategy is to compile /fpe:0 and allow traps on floating-point exceptions, you need to take action to unmask the traps in the floating-point control word because most other languages mask traps by default.

Recall that a Fortran Console or Fortran QuickWin (or Standard Graphics) application would have unmasked traps for you automatically because the Fortran run-time system provides the main program and calls your MAIN__ which executes some prolog code before the actual application code starts. You do not have that in a Fortran DLL called by some other language. Different languages establish different initial environments. You must provide the desired initial environment yourself. The samples discussed below and in the discussion of Fortran Windows applications give some ideas on how and when to do this.

Sample Program VBVF1

The following discussion refers to the Visual Fortran sample program VBVF1 provided in the ...\SAMPLES\EXCEPTIONHANDLING folder. VBVF1 shows how you might incorporate structured exception handling and standard Fortran I/O error handling techniques to protect a Visual BASIC GUI from exceptions and errors occurring in a Fortran DLL called by the GUI. The error handling techniques are general in nature and could be used in other situations as well. For example, a C main program calling a Fortran DLL could also use the ideas presented here.

Sample application VBVF1 attempts to contain the effects of unexpected errors and exceptions occurring in a Fortran DLL by turning the unexpected events into user defined status conditions which the Visual Basic code can deal with gracefully.

The GUI has six buttons each of which generates a particular error condition in the Fortran DLL. A seventh button allows you to clear the error status displayed in the GUI. There are three text boxes in the GUI. One box displays a message associated with the last error. Another box displays an error number for the last error and the third box displays the Visual Basic source file where the error occurred.

Two of the buttons, "Test Integer Divide By Zero Response" and "Test Floating Point Overflow Response," are coded to show the use of a Visual Basic handler to deal with an error occurring in the Fortran DLL. While Visual Basic error handling works for these two specific cases to some extent, it is not a very robust solution for dealing with the more general case. If you are not familiar with Visual Basic error handling, this simple example should begin to give you an idea of what can be done. Refer to Visual Basic documentation for more complete information.

Each of the four remaining buttons causes a call to routine Fortran_Test in the Fortran DLL. See source file gen_errs.f90. Routine Fortran_Test generates the selected error. The Visual Basic code does not call Fortran_Test directly however. All Visual Basic calls to Fortran_Test go through a wrapper routine, Fortran_Test_Wrapper. Fortran_Test_Wrapper (source file wrappers.c) wraps a C try-except construct around calls into the DLL and acts as a control point for containing exceptions and errors that may unexpectedly occur in the DLL. Fortran_Test_Wrapper always returns a user defined application specific status to the Visual Basic calling routine. The Visual Basic code then reacts to the status value by displaying some appropriate message to the user.

The implementation of this scheme depends on two things:

  1. The solid programming practice of always requesting and checking I/O status in Fortran I/O statements
  2. The use of the try-except construct in the wrapper routine.

In the __try block of the wrapper routine, a call is made to Fortran_Test, which expects a Fortran defined status value to be returned. For the test cases that generate an I/O error, the code in Fortran_Test captures the I/O status and returns it to Fortran_Test_Wrapper. Fortran_Test_Wrapper then calls another DLL routine, TRANSLATE_TO_MY_APP_CODES, which does a table look up to translate the Fortran defined status code to a user defined status code. Execution continues with the return statement following the __except block. The Visual Basic code gets the user defined status value and puts up the appropriate display.

If an exception occurs during the execution of Fortran_Test, control transfers to the operating system. The operating system examines its list of exception handlers and finds the exception filter associated with Fortran_Test_Wrapper. It evaluates the __except expression to see if the exception will be handled here. The __except expression calls another DLL routine, CHECK_EXCEPTION_INFO, passing it the operating system supplied information about the exception. CHECK_EXCEPTION_INFO determines what the exception condition was, generates traceback information with an appropriate message specific to the event and returns EXCEPTION_EXECUTE_HANDLER.

Returning EXCEPTION_EXECUTE_HANDLER tells the operating system to resume execution in the handler block associated with the __except construct in Fortran_Test_Wrapper. The code in the handler block calls TRANSLATE_TO_MY_APP_CODES, which again does a table look up, but to a second table, to translate the Windows defined status code to a user defined status code. Execution continues with the return statement following the __except block. The Visual Basic code gets the user defined status value and puts up the appropriate display.

In this sample, the Fortran code is compiled /fpe:0 and intentionally creates floating point exceptions. It expects traps due to those exceptions. By default, Visual Basic masks all floating point traps so the application must take action to unmask traps. For this sample, two routines are provided in the DLL, Fortran_FP_Trap_Enable and Fortran_FP_Trap_Disable, that are called from the Visual Basic code. In procedure cmdFltOvf_Click, source file frmVBVF1.frm, there is a call to DLL routine Fortran_FP_Trap_Enable to unmask traps before the call to DLL routine Fortran_FltOvf which generates the exception.

Fortran_FP_Trap_Enable uses GETCONTROLFPQQ and SETCONTROLFPQQ to unmask traps in the floating-point control word. In the Visual Basic handler code for this routine at label My_VB_FltOvf_Handler, DLL routine Fortran_FP_Trap_Disable is called to revert to Visual Basic default exception settings. The timing of when and how to unmask or mask floating-point traps is an application design issue. The sample just shows one way you can do it.

Custom Handlers for Fortran Windows Applications

Fortran Windows applications are not hooked up to the Fortran default exception handling processing facilities. Fortran Windows applications are considered to be an area devoted to full customization, and the Fortran run-time system tries to "stay out of the way," so you can do whatever you want in your code.

The following discussion refers to the sample program WINEXCP1 provided with the Visual Fortran kit in the ...\SAMPLES\EXCEPTIONHANDLING folder. WINEXCP1 extends the GENERIC sample to show how you might incorporate structured exception handling and standard I/O error handling techniques to protect a Fortran message processing routine.

If you build and run this application, the main window will be displayed and you will see a menu labeled "Run Error Simulator." On the error simulator menu you can choose from six different error generating test cases. Selecting any one of the test cases sends a specific message which is interpreted in function MainWndProc in source file generic.f90. For each message, MainWndProc effectively calls the message processing routine ERROR_SIMULATOR to generate the selected condition. But MainWndProc does not call ERROR_SIMULATOR directly. Instead, it calls a wrapper routine, ERROR_SIMULATOR_WRAPPER in source file wrapper.c, which wraps a try-except construct around the call to ERROR_SIMULATOR. Also notice that MainWndProc expects to get a status value returned from ERROR_SIMULATOR_WRAPPER. If ERROR_SIMULATOR_WRAPPER returns a zero, all is well. If a non-zero status is returned, MainWndProc displays a message box indicating the error simulator had an error.

The important point to notice is that errors in the simulator routine are contained and turned into status codes that can be acted upon rather than allowing the program to abort when something unexpected occurs. To achieve this behavior, the code must do two things:

  1. The simulator must always ask for and test the completion status on Fortran I/O statements. If the I/O status is not FOR$IOS_SUCCESS, the simulator takes appropriate action and returns the non-zero status to MainWndProc.

  2. Exceptions must be caught and dealt with. The __except construct in ERROR_SIMULATOR_WRAPPER allows this to happen. When an exception occurs in ERROR_SIMULATOR, the operating system will evaluate the __except filter expression to see if we want to handle the exception. The filter expression is a call to CHECK_EXCEPTION_INFO in generic1.f90. A pointer to the operating system supplied exception information is passed to CHECK_EXCEPTION_INFO.

    If CHECK_EXCEPTION_INFO returns EXCEPTION_EXECUTE_HANDLER, the code in the __except handler block is executed. The handler block sets the return status to a non-zero value. Execution then continues with the return statement following the handler block and returns to MainWndProc. CHECK_EXCEPTION_INFO takes whatever error handling action is appropriate for the situation. In this sample program, it will clear the floating point exception status for floating point exceptions and in all cases, it will generate a traceback output with a message specific to the exception which occurred.

If CHECK_EXCEPTION_INFO returns EXCEPTION_CONTINUE_SEARCH, the operating system looks for any other handlers on its handler list. In this case it would find the C run-time system default __except filter which calls the C run-time routine _XcptFilter. _XcptFilter would return EXCEPTION_EXECUTE_HANDLER, which would call the C run-time _exit routine to abort the process.

Finally, recall that the Fortran run-time system does not supply a C main routine for a Fortran Windows application as in a Fortran Console or Fortran QuickWin application. In those cases, main would have called MAIN__, which would have executed a few compiler generated Fortran run-time system calls to do some additional initialization work. This application compiles with the /fpe:0 option so that floating-point traps can be demonstrated. The application must unmask these traps since the Fortran run-time system will not be called to do it automatically. For this sample, the traps are unmasked at the beginning of ERROR_SIMULATOR using GETCONTROLFPQQ and SETCONTROLFPQQ. The traps would have been masked by default.

In some situations, you may want a separate initialization routine to unmask traps for the entire duration of your application. In other cases, you may want to mask or unmask traps at selective points during program execution. If you selectively mask and unmask traps, keep in mind that your code may set floating point exception status bits even through you may have traps masked. The floating-point status bits are "sticky," so before you unmask traps again, you should clear the floating point status bits so you do not generate a false trap later in your code.