Example: 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.Program 4-2, 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 a large buffer (sufficient for the entire file) is allocated for each file. Furthermore, both the input and output files are locked to ensure that no other process can modify either file during processing and that the new output file is an accurate transformation of the input file. Thus, there are multiple possible failure points for each file that is processed, 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. Program 4-2 achieves this and ensures that the files are unlocked in all cases without resorting to the elaborate control flow methods that would be necessary without SEH. More extensive comments are included in the code from the book's Web site.
Program 4-2. toupper: File Processing with Error Recovery
/* Chapter 4. toupper command. */
/* Convert one or more files, changing all letters to uppercase.
The output file will be the same name as the input file, except
a UC_ prefix will be attached to the file name. */
#include "EvryThng.h"
int _tmain (DWORD argc, LPTSTR argv [])
{
HANDLE hIn = INVALID_HANDLE_VALUE, hOut = INVALID_HANDLE_VALUE;
DWORD FileSize, nXfer, iFile, j;
CHAR OutFileName [256] = ", *pBuffer = NULL;
OVERLAPPED ov == {0, 0, 0, 0, NULL}; /* Used for file locks. */
if (argc <= 1)
ReportError (_T ("Usage: toupper files"), 1, FALSE);
/* Process all files on the command line. */
for (iFile = 1; iFile < argc; iFile++) __try { /* Excptn block. */
/* All file handles are invalid, pBuffer == NULL, and
OutFileName is empty. This is ensured by the handlers. */
_stprintf (OutFileName, "UC_%s", argv [iFile]);
__try { /* Inner try-finally block. */
/* An error at any step will raise an exception, */
/* and the next file will be processed after cleanup. */
/* Amount of cleanup depends on where the error occurs. */
/* Create the output file (fail if file exists). */
hIn = CreateFile (argv [iFile], GENERIC_READ, 0,
NULL, OPEN_EXISTING, 0, NULL);
if (hIn == INVALID_HANDLE_VALUE)
ReportException (argv [iFile], 1);
FileSize = GetFileSize (hIn, NULL);
hOut = CreateFile (OutFileName,
GENERIC_READ | GENERIC_WRITE, 0, NULL,
CREATE_NEW, 0, NULL);
if (hOut == INVALID_HANDLE_VALUE)
ReportException (OutFileName, 1);
/* Allocate memory for the file contents. */
pBuffer = malloc (FileSize);
if (pBuffer == NULL)
ReportException (_T ("Memory allocation error"), 1);
/* Lock both files to ensure integrity of the copy. */
if (!LockFileEx (hIn, LOCKFILE_FAIL_IMMEDIATELY, 0,
FileSize, 0, &ov)
ReportException (_T ("Input file lock error"), 1);
if (!LockFileEx (hOut,
LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY,
0, FileSize, 0, &ov)
ReportException (_T ("Output file lock error"), 1);
/* Read data, convert, and write to the output file. */
/* Free resources on completion or error; */
/* process next file. */
if (!ReadFile (hIn, pBuffer, FileSize, &nXfer, NULL))
ReportException (_T ("ReadFile error"), 1);
for (j = 0; j < FileSize; j++) /* Convert data. */
if (isalpha (pBuffer [j]))
pBuffer [j] = toupper (pBuffer [j]);
if (!WriteFile (hOut, pBuffer, FileSize, &nXfer, NULL))
ReportException (_T ("WriteFile error"), 1);
} __finally { /* Locks are released, file handles closed, */
/* memory freed, and handles and pointer reinitialized. */
if (pBuffer != NULL) free (pBuffer); pBuffer = NULL;
if (hIn != INVALID_HANDLE_VALUE) {
UnlockFileEx (hIn, 0, FileSize, 0, &ov);
CloseHandle (hIn);
hIn = INVALID_HANDLE_VALUE;
}
if (hOut != INVALID_HANDLE_VALUE) {
UnlockFileEx (hOut, 0, FileSize, 0, &ov);
CloseHandle (hOut);
hOut = INVALID_HANDLE_VALUE;
}
_tcscpy (OutFileName, _T ("));
}
} /* End of main file processing loop and try block. */
/* This exception handler applies to the loop body. */
__except (EXCEPTION_EXECUTE_HANDLER) {
_tprintf (_T ("Error processing file %s\n"), argv [iFile]);
DeleteFile (OutFileName);
}
_tprintf (_T ("All files converted, except as noted above\n"));
return 0;
}