结构化异常处理(SEH)包含两方面的功能:终止处理和异常处理。
终止处理程序确保不管一个代码块是如何退出的,另一个代码块总能被调用和执行。终止处理的语法如下:
__try
{
// guarded body of code (被保护代码)
}
__finally
{
// __finally block (终止处理代码)
}
操作系统和编译器的协同工作保证了不管被保护代码部分是如何退出的---无论是在保护代码中使用return、还是goto、或者longjump语句---终止处理程序都会被调用,即__finally代码块都能执行。(除非在保护代码中调用ExitProcess、ExitThread、TerminateProcess、TerminateThread来终止进程或线程)
实例代码如下:
LPTSTR lpBuffer = NULL;
CRITICAL_SECTION CriticalSection;
// EnterCriticalSection synchronizes code with other threads.
EnterCriticalSection(&CriticalSection);
__try
{
// Perform a task that may cause an exception.
lpBuffer = (LPTSTR) LocalAlloc(LPTR, 10);
StringCchCopy(lpBuffer, 10, TEXT("Hello"));
_tprintf(TEXT("%s/n"),lpBuffer);
LocalFree(lpBuffer);
}
__finally
{
// LeaveCriticalSection is called even if an exception occurred.
LeaveCriticalSection(&CriticalSection);
}
编译器怎样保证finally块可以在try代码块退出前被执行呢?原来当编译器检查程序代码时,如果发现try代码块里有一个return语句,于是编译器会生成一些代码先将返回值保存在由它创建的临时变量里,然后再执行finally代码块,这个过程称为局部展开。一旦finally代码块执行完毕,编译器所创建的变量的值就会返回给函数的调用者。实际上,应该尽量避免在try代码块中使用return语句,因为这对应用程序性能是有害的。
如果代码控制流正常地离开try代码块进入finally代码块,那么进入finally代码块的额外开销是最小的。为了尽可能避免写出让try块提前退出的代码,Microsoft为它的C/C++编译器加入了一个关键字:__leave:
关键字__leave会导致代码执行控制流跳转到try块的结尾。因为在这种情况下,代码执行将正常地从try块进入finally块,所以不会产生额外的开销。
实例代码如下:
DWORD ASCEFunc()
{
HANDLE hFile = INVALID_HANDLE_VALUE;
PVOID pvBuf = NULLL;
BOOL bFunctionOk = FALSE;
__try
{
DWORD dwNumBytesRead;
BOOL bOk;
hFile = CreateFile(TEXT("asce.dat"), GENERIC_READ, FILE_SHARE_MODE,
NULL, OPEN_EXISTING, 0, NULL);
if(hFIle == NULL)
__leave;
pvBuf = VirtualAlloc(NULL, 1024, MEM_COMMIT, PAGE_READWRITE);
if(pvBuf == NULL)
__leave;
bOk = ReadFile(hFile, pvBuf, 1024, &dwNumBytesRead, NULL);
if(!bOk || (dwNumBytesRead == 0))
__leave;
//Do some operation here
//成功返回标志
bFunctionOk = TRUE;
}
__finally
{
if(pvBuf != NULL)
VirtualFree(pvBuf, MEM_RELEASE | MEM_DECOMMIT);
if(hFile != INVALID_HANDLE_VALUE)
CloseHandle(hFile);
}
return bFunctionOk;
}
总结,三种会引起finally块执行的情形:
1)从try块到finally块的正常代码控制流;
2)局部展开:从try块的提前退出(由goto、longjump、continue、break、return等语句引起)将程序控制流强制转入finally块;
3)全局展开。
finally块的执行总是由以上三种情况之一引起的,要确定是哪一种,可以调用内在函数AbnormalTermination:
BOOL AbnormalTermination(void);
注意:我们只能在finally块里调用这个内在函数,它将返回一个布尔值来表明一个与当前finally块相关的try块是否已经提前退出。即如果代码执行从try块正常流入finally块,函数的返回值是FALSE;如果控制流从try块中异常退出---通常是try块中goto、break、return或continue语句导致了局部展开,或者因为try块中代码抛出了内存访问违规或其他异常引起全局展开---那么AbnormalTermination函数返回TRUE。但进一步区分是全局展开还是局部展开是不可能的。
实例代码如下:
/******************************************************************************
Module: SEHTerm.cpp
Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre
******************************************************************************/
#include <windows.h>
#include <tchar.h>
///////////////////////////////////////////////////////////////////////////////
BOOL IsWindowsVista() {
// Prepare the OSVERSIONINFOEX structure to indicate Windows Vista.
OSVERSIONINFOEX osver = { 0 };
osver.dwOSVersionInfoSize = sizeof(osver);
osver.dwMajorVersion = 6;
osver.dwMinorVersion = 0;
osver.dwPlatformId = VER_PLATFORM_WIN32_NT;
// Prepare the condition mask.
DWORDLONG dwlConditionMask = 0; // You MUST initialize this to 0.
VER_SET_CONDITION(dwlConditionMask, VER_MAJORVERSION, VER_EQUAL);
VER_SET_CONDITION(dwlConditionMask, VER_MINORVERSION, VER_EQUAL);
VER_SET_CONDITION(dwlConditionMask, VER_PLATFORMID, VER_EQUAL);
// Perform the version test.
if (VerifyVersionInfo(&osver, VER_MAJORVERSION | VER_MINORVERSION |
VER_PLATFORMID, dwlConditionMask)) {
// The host system is Windows Vista exactly.
return(TRUE);
} else {
// The host system is NOT Windows Vista.
return(FALSE);
}
}
void TriggerException() {
__try {
int n = MessageBox(NULL, TEXT("Perform invalid memory access?"),
TEXT("SEHTerm: In try block"), MB_YESNO);
if (n == IDYES) {
* (PBYTE) NULL = 5; // This causes an access violation
}
}
__finally {
PCTSTR psz = AbnormalTermination()
? TEXT("Abnormal termination") : TEXT("Normal termination");
MessageBox(NULL, psz, TEXT("SEHTerm: In finally block"), MB_OK);
}
MessageBox(NULL, TEXT("Normal function termination"),
TEXT("SEHTerm: After finally block"), MB_OK);
}
int WINAPI _tWinMain(HINSTANCE, HINSTANCE, PTSTR, int) {
// In Windows Vista, a global unwind occurs if an except filter
// returns EXCEPTION_EXECUTE_HANDLER. If an unhandled exception
// occurs, the process is simply terminated and the finally blocks
// are not exectuted.
if (IsWindowsVista()) {
DWORD n = MessageBox(NULL, TEXT("Protect with try/except?"),
TEXT("SEHTerm: workflow"), MB_YESNO);
if (n == IDYES) {
__try {
TriggerException();
}
__except (EXCEPTION_EXECUTE_HANDLER) {
// But the system dialog will not appear.
// So, popup a message box.
MessageBox(NULL, TEXT("Abnormal process termination"),
TEXT("Process entry point try/except handler"), MB_OK);
// Exit with a dedicated error code
return(-1);
}
} else {
TriggerException();
}
} else {
TriggerException();
}
MessageBox(NULL, TEXT("Normal process termination"),
TEXT("SEHTerm: before leaving the main thread"), MB_OK);
return(0);
}
使用终止处理程序的理由:
1)因为清理工作集中在一个地方执行,并且保证能得到执行,从而简化了错误处理;
2)提高代码的可读性;
3)让代码更容易维护;
4)如果正确使用,它对程序性能和体积的影响是微小的。