1. Using Termination Handlers to Improve Program Quality
Termination and exception handlers allow you to
make your program more robust by both simplifying recovery from errors
and exceptions and helping to ensure that resources and file locks are
freed at critical junctures.
Toupper, illustrates these points, using ideas from the preceding code fragments. toupper
processes multiple files, as specified on the command line, rewriting
them so that all letters are in uppercase. Converted files are named by
prefixing UC_ to the original file name, and the program
“specification” states that an existing file should not be overridden.
File conversion is performed in memory, so there is a large buffer
(sufficient for the entire file) allocated for each file. There are
multiple possible failure points for each processed file, but the
program must defend against all such errors and then recover and attempt
to process all the remaining files named on the command line.
Note that this program depends on file sizes, so it will not work on objects for which GetFileSizeEx fails, such as a named pipe . Furthermore, it fails for large text files longer than 4GB.
The code in the Examples file has more extensive comments.
Run 1 shows toupper operation. Originally, there are two text files, a.txt and b.txt. The cat programdisplays the contents of these two files; you could also use the Windows type command. toupper converts these two files, continuing after failing to find b.txt. Finally, cat displays the two converted files, UC_a.txt and UC_c.txt.
2. Using a Filter Function
Program 2
is a skeleton program that illustrates exception and termination
handling with a filter function. This example prompts the user to
specify the exception type and then proceeds to generate an exception.
The filter function disposes of the different exception types in various
ways; the selections here are arbitrary and intended simply to
illustrate the possibilities. In particular, the program diagnoses
memory access violations, giving the virtual address of the reference.
Program 2. Exception: Processing Exceptions and Termination
#include "Everything.h"
#include <float.h>
DWORD Filter(LPEXCEPTION_POINTERS, LPDWORD);
double x = 1.0, y = 0.0;
int _tmain(int argc, LPTSTR argv[])
{
DWORD eCategory, i = 0, ix, iy = 0;
LPDWORD pNull = NULL;
BOOL done = FALSE;
DWORD fpOld, fpNew;
fpOld = _controlfp(0, 0); /* Save old control mask. */
/* Enable floating-point exceptions. */
fpNew = fpOld & ~(EM_OVERFLOW | EM_UNDERFLOW | EM_INEXACT
| EM_ZERODIVIDE | EM_DENORMAL | EM_INVALID);
_controlfp(fpNew, MCW_EM);
while (!done) __try { /* Try-finally. */
_tprintf(_T("Enter exception type: "));
_tprintf(_T
(" 1: Mem, 2: Int, 3: Flt 4: User 5: __leave "));
_tscanf(_T("%d"), &i);
__try { /* Try-except block. */
switch (i) {
case 1: /* Memory reference. */
ix = *pNull; *pNull = 5; break;
case 2: /* Integer arithmetic. */
ix = ix / iy; break;
case 3: /* Floating-point exception. */
x = x / y;
_tprintf(_T("x = %20e\n"), x); break;
case 4: /* User-generated exception. */
ReportException(_T("User exception"), 1); break;
case 5: /* Use the _leave statement to terminate. */
__leave;
default: done = TRUE;
}
} /* End of inner __try. */
__except (Filter(GetExceptionInformation(), &eCategory))
{
switch (eCategory) {
case 0:
_tprintf(_T("Unknown Exception\n")); break;
case 1:
_tprintf(_T("Memory Ref Exception\n")); continue;
case 2:
_tprintf(_T("Integer Exception\n")); break;
case 3:
_tprintf(_T("Floating-Point Exception\n"));
_clearfp(); break;
case 10:
_tprintf(_T("User Exception\n")); break;
default:
_tprintf( _T("Unknown Exception\n")); break;
} /* End of switch statement. */
_tprintf(_T("End of handler\n"));
} /* End of try-except block. */
} /* End of While loop -- the termination handler is below. */
__finally { /* This is part of the while loop. */
_tprintf(_T("Abnormal Termination?: %d\n"),
AbnormalTermination());
}
_controlfp(fpOld, 0xFFFFFFFF); /* Restore old FP mask.*/
return 0;
}
|
The __finally
block restores the state of the floating-point mask. Restoring state, as
done here, is not important when the process is about to terminate, but
it is important later when a thread is terminated. In general, a
process should still restore system resources by, for example, deleting
temporary files and releasing synchronization resources and file locks . Program 3 shows the filter function.
Program 3. Filter: Exception Filtering
static DWORD Filter(LPEXCEPTION_POINTERS pExP, LPDWORD eCategory)
/* Categorize the exception and decide action. */
{
DWORD exCode, readWrite, virtAddr;
exCode = pExP->ExceptionRecord->ExceptionCode;
_tprintf(_T("Filter. exCode: %x\n"), exCode);
if ((0x20000000 & exCode) != 0) { /* User exception. */
*eCategory = 10;
return EXCEPTION_EXECUTE_HANDLER;
}
switch (exCode) {
case EXCEPTION_ACCESS_VIOLATION:
readWrite = /* Was it a read, write, execute? */
pExP->ExceptionRecord->ExceptionInformation[0];
virtAddr = /* Virtual address of the violation. */
pExP->ExceptionRecord->ExceptionInformation[1];
_tprintf(
_T("Access Violation. Read/Write/Exec: %d. Address: %x\n"),
readWrite, virtAddr);
*eCategory = 1;
return EXCEPTION_EXECUTE_HANDLER;
case EXCEPTION_INT_DIVIDE_BY_ZERO:
case EXCEPTION_INT_OVERFLOW:
*eCategory = 2;
return EXCEPTION_EXECUTE_HANDLER;
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
case EXCEPTION_FLT_OVERFLOW:
_tprintf(_T("Flt Exception - large result.\n"));
*eCategory = 3;
_clearfp();
return EXCEPTION_EXECUTE_HANDLER;
default:
*eCategory = 0;
return EXCEPTION_CONTINUE_SEARCH;
}
}
|
This example does not illustrate memory allocation exceptions.
Run 3, after the filter function (Program 3) shows the program operation.
Program 3 shows the filter function used in Program 2. This function simply checks and categorizes the various possible exception code values. The code in the Examples file checks every possible value; here the function tests only for a few that are relevant to the test program.