The function terminates the
process when the programmer indicates that the error is fatal. This
approach, however, prevents an orderly shutdown, and it also prevents
program continuation after recovering from an error. For example, the
program may have created temporary files that should be deleted, or the
program may simply proceed to do other work after abandoning the failed
task. ReportError has other limitations, including the following.
A fatal error shuts down the entire process when only a single thread should terminate. You may wish to continue program execution rather than terminate the process. Synchronization resources , such as events or semaphores, will not be released in many circumstances.
Open handles will be closed by a terminating
process, but not by a terminating thread. It is necessary to address
this and other deficiencies.
The solution is to write a new function that invokes ReportError with a nonfatal code in order to generate the error message. Next, on a
fatal error, it will raise an exception. Windows will use an exception
handler from the calling try block, so the exception may not actually be
fatal if the handler allows the program to recover and resume.
Essentially, ReportException augments normal defensive programming techniques, previously limited to ReportError. Once a problem is detected, the exception handler allows the program to recover and continue after the error. Program 4-2 illustrates this capability.
Program 1 shows the function. It is in the same source module as ReportError, so the definitions and include files are omitted.
Program 1. ReportException: Exception Reporting Function
/* ReportError extension to generate a nonfatal user-exception code. */
VOID ReportException(LPCTSTR userMessage, DWORD exceptionCode)
{
ReportError(userMessage, 0, TRUE);
if (exceptionCode != 0) /* If fatal, raise an exception. */
RaiseException(
(0x0FFFFFFF & exceptionCode) | 0xE0000000, 0, 0, NULL);
return;
}
|
ReportException is used in Program 2 and elsewhere.
Program 2. toupper: File Processing with Error and Exception Recovery
/* 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 "Everything.h"
int _tmain(DWORD argc, LPTSTR argv[])
{
HANDLE hIn = INVALID_HANDLE_VALUE, hOut = INVALID_HANDLE_VALUE;
DWORD nXfer, iFile, j;
CHAR outFileName[256] = "", *pBuffer = NULL;
OVERLAPPED ov = { 0, 0, 0, 0, NULL};
LARGE_INTEGER fSize;
/* Process all files on the command line. */
for (iFile = 1; iFile < argc; iFile++) __try { /* Exceptn block */
/* All file handles are invalid, pBuffer == NULL, and
outFileName is empty. This is assured by the handlers */
if (_tcslen(argv[iFile]) > 250)
ReportException(_T("The file name is too long."), 1);
_stprintf(outFileName, "UC_%s", argv[iFile]);
__try { /* Inner try-finally block */
hIn = CreateFile(argv[iFile], GENERIC_READ,
0, NULL, OPEN_EXISTING, 0, NULL);
if (hIn == INVALID_HANDLE_VALUE)
ReportException(argv[iFile], 1);
if (!GetFileSizeEx(hIn, &fSize) || fSize.HighPart > 0)
ReportException(_T("This file is too large."), 1);
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(fSize.LowPart);
if (pBuffer == NULL)
ReportException(_T("Memory allocation error"), 1);
/* Read the data, convert it, and write to the output file */
/* Free all resources on completion; process next file */
if (!ReadFile(hIn, pBuffer, fSize.LowPart, &nXfer, NULL)
|| (nXfer != fSize.LowPart))
ReportException(_T("ReadFile error"), 1);
for (j = 0; j < fSize.LowPart; j++) /* Convert data */
if (isalpha(pBuffer[j])) pBuffer[j] =
toupper(pBuffer[j]);
if (!WriteFile(hOut, pBuffer, fSize.LowPart, &nXfer, NULL)
|| (nXfer != fSize.LowPart))
ReportException(_T("WriteFile error"), 1);
} __finally { /* File handles are always closed */
/* memory freed, and handles and pointer reinitialized. */
if (pBuffer != NULL) free(pBuffer); pBuffer = NULL;
if (hIn != INVALID_HANDLE_VALUE) {
CloseHandle(hIn);
hIn = INVALID_HANDLE_VALUE;
}
if (hOut != INVALID_HANDLE_VALUE) {
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;
}
|
The UNIX signal model is significantly different
from SEH. Signals can be missed or ignored, and the flow is different.
Nonetheless, there are points of comparison.
UNIX signal handling is largely supported through
the C library, which is also available in a limited implementation
under Windows. In many cases, Windows programs can use console control
handlers, in place of
signals.
Some signals correspond to Windows exceptions.
Here is the limited signal-to-exception correspondence:
SIGILL—EXCEPTION_PRIV_INSTRUCTION or EXCEPTION_ILLEGAL_INSTRUCTION SIGSEGV—EXCEPTION_ACCESS_VIOLATION SIGFPE—Seven distinct floating-point exceptions, such as EXCEPTION_FLT_DIVIDE_BY_ZERO SIGUSR1 and SIGUSR2—User-defined exceptions
The C library raise function corresponds to RaiseException.
Windows will not generate SIGILL, SIGSEGV, or SIGTERM, although raise can generate one of them. Windows does not support SIGINT.
The UNIX kill function (kill is not in the Standard C library), which can send a signal to another process, is comparable to the Windows function GenerateConsoleCtrlEvent . In the limited case of SIGKILL, there is no corresponding exception, but Windows has TerminateProcess and TerminateThread, allowing one process (or thread) to “kill” another, although these functions should be used with care .
|