您的位置:首页 > 其它

让程序在崩溃时体面的退出之终极解决方案(SEH+Dump+Unhandled Exception Filter)

2011-08-05 13:48 453 查看
/article/1360333.html

--------------------------------------------------

在我的上篇文章《让程序在崩溃时体面的退出之SEH+Dump文件》我介绍了怎样用SEH加上Dump文件来避免程序的崩溃并在程序崩溃时创建Dump文件来帮助定位出现异常的代码行。可是只有try/except块中try块中的代码出现异常才能被捕捉到,try块外面的代码出现异常,程序照样会崩溃。

下面用《让程序在崩溃时体面的退出之SEH+Dump文件》文中的代码为例子来说明。

view plainprint?

// 创建Dump文件

//

void CreateDumpFile(LPCWSTR lpstrDumpFilePathName, EXCEPTION_POINTERS *pException)

{

// 创建Dump文件

//

HANDLE hDumpFile = CreateFile(lpstrDumpFilePathName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

// Dump信息

//

MINIDUMP_EXCEPTION_INFORMATION dumpInfo;

dumpInfo.ExceptionPointers = pException;

dumpInfo.ThreadId = GetCurrentThreadId();

dumpInfo.ClientPointers = TRUE;

// 写入Dump文件内容

//

MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, MiniDumpNormal, &dumpInfo, NULL, NULL);

CloseHandle(hDumpFile);

}

// 作为except块中表达式的函数

//

LONG CrashHandler(EXCEPTION_POINTERS *pException)

{

// 在这里添加处理程序崩溃情况的代码

//

// 这里以弹出一个对话框为例子

//

MessageBox(NULL, _T("Message from Catch handler"), _T("Test"), MB_OK);

// 创建Dump文件

//

CreateDumpFile(_T("C:\\Test.dmp"), pException);

return EXCEPTION_EXECUTE_HANDLER;

}

int _tmain(int argc, _TCHAR* argv[])

{

__try

{

MessageBox(NULL, _T("Message from '__try' section"), _T("Test"), MB_OK);

// 除零,人为的使程序崩溃

//

int i = 13;

int j = 0;

int m = i / j;

}

// 捕捉到让程序崩溃的异常时创建Dump文件

//

__except(CrashHandler(GetExceptionInformation()))

{

// 这里以弹出一个对话框为例子

//

MessageBox(NULL, _T("Message from '__except' section"), _T("Test"), MB_OK);

}

MessageBox(NULL, _T("Funcation completed"), _T("Test"), MB_OK);

return 0;

}

// 创建Dump文件
//
void CreateDumpFile(LPCWSTR lpstrDumpFilePathName, EXCEPTION_POINTERS *pException)
{
// 创建Dump文件
//
HANDLE hDumpFile = CreateFile(lpstrDumpFilePathName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

// Dump信息
//
MINIDUMP_EXCEPTION_INFORMATION dumpInfo;
dumpInfo.ExceptionPointers = pException;
dumpInfo.ThreadId = GetCurrentThreadId();
dumpInfo.ClientPointers = TRUE;

// 写入Dump文件内容
//
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, MiniDumpNormal, &dumpInfo, NULL, NULL);

CloseHandle(hDumpFile);
}

// 作为except块中表达式的函数
//
LONG CrashHandler(EXCEPTION_POINTERS *pException)
{
// 在这里添加处理程序崩溃情况的代码
//

// 这里以弹出一个对话框为例子
//
MessageBox(NULL, _T("Message from Catch handler"), _T("Test"), MB_OK);

// 创建Dump文件
//
CreateDumpFile(_T("C:\\Test.dmp"), pException);

return EXCEPTION_EXECUTE_HANDLER;
}

int _tmain(int argc, _TCHAR* argv[])
{
__try
{
MessageBox(NULL, _T("Message from '__try' section"), _T("Test"), MB_OK);

// 除零,人为的使程序崩溃
//
int i = 13;
int j = 0;
int m = i / j;
}
// 捕捉到让程序崩溃的异常时创建Dump文件
//
__except(CrashHandler(GetExceptionInformation()))
{
// 这里以弹出一个对话框为例子
//
MessageBox(NULL, _T("Message from '__except' section"), _T("Test"), MB_OK);
}

MessageBox(NULL, _T("Funcation completed"), _T("Test"), MB_OK);

return 0;
}


编译上面的代码并运行,会依次弹出下面这些对话框,并在C盘创建一个Dump文件Test.dmp。









如果把上面代码中的main()函数改成下面的样子,运行编译后的程序依然会崩溃。

view plainprint?

int _tmain(int argc, _TCHAR* argv[])

{

__try

{

MessageBox(NULL, _T("Message from '__try' section"), _T("Test"), MB_OK);

// 除零,人为的使程序崩溃

//

int i = 13;

int j = 0;

int m = i / j;

}

// 捕捉到让程序崩溃的异常时创建Dump文件

//

__except(CrashHandler(GetExceptionInformation()))

{

// 这里以弹出一个对话框为例子

//

MessageBox(NULL, _T("Message from '__except' section"), _T("Test"), MB_OK);

}

// 除零,人为的使程序崩溃

//

int i = 13;

int j = 0;

int m = i / j;

MessageBox(NULL, _T("Funcation completed"), _T("Test"), MB_OK);

return 0;

}

int _tmain(int argc, _TCHAR* argv[])
{
__try
{
MessageBox(NULL, _T("Message from '__try' section"), _T("Test"), MB_OK);

// 除零,人为的使程序崩溃
//
int i = 13;
int j = 0;
int m = i / j;
}
// 捕捉到让程序崩溃的异常时创建Dump文件
//
__except(CrashHandler(GetExceptionInformation()))
{
// 这里以弹出一个对话框为例子
//
MessageBox(NULL, _T("Message from '__except' section"), _T("Test"), MB_OK);
}

// 除零,人为的使程序崩溃
//
int i = 13;
int j = 0;
int m = i / j;

MessageBox(NULL, _T("Funcation completed"), _T("Test"), MB_OK);

return 0;
}


这种情况在实际编程中是很有可能出现的,毕竟我们不可能事先预计到所有可能出现导致程序崩溃的情况,并把这些代码放到try/except块中。那么对于这些不可预知的异常该怎么办呢?这就要用到我那篇《让程序在崩溃时体面的退出之Unhandled Exception》中的方法:用Windows API中的SetUnhandledExceptionFilter设置一个回调函数来处理这些无法预料的异常。下面是在上面的例子代码上修改后的代码。其中函数CreateDumpFile没有任何变化。

view plainprint?

// 得到当前时间

//

wstring GetPresentTime()

{

SYSTEMTIME time;

GetLocalTime(&time);

wchar_t wszTime[128];

swprintf_s(wszTime, _T("%04d-%02d-%02d %02d-%02d-%02d-%03d"), time.wYear, time.wMonth, time.wDay, time.wHour, time.wMinute, time.wSecond, time.wMilliseconds);

return wstring(wszTime);

}

// 处理异常的回调函数

//

LONG CrashHandler(EXCEPTION_POINTERS *pException)

{

// 在这里添加处理程序崩溃情况的代码

//

// 这里以弹出一个对话框为例子

//

MessageBox(NULL, _T("Message from Catch handler"), _T("Test"), MB_OK);

// 以当前时间为文件名

//

wstring strDumpFileName = _T("C:\\") + GetPresentTime() +_T(".dmp");

// 创建Dump文件

//

CreateDumpFile(strDumpFileName.data(), pException);

return EXCEPTION_EXECUTE_HANDLER;

}

int _tmain(int argc, _TCHAR* argv[])

{

// 设置处理Unhandled Exception的回调函数

//

SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)CrashHandler);

__try

{

MessageBox(NULL, _T("Message from '__try' section"), _T("Test"), MB_OK);

// 除零,人为的使程序崩溃

//

int i = 13;

int j = 0;

int m = i / j;

}

__except(CrashHandler(GetExceptionInformation()))

{

// 在这里添加处理程序崩溃情况的代码

//

// 这里以弹出一个对话框为例子

//

MessageBox(NULL, _T("Message from '__except' section"), _T("Test"), MB_OK);

}

// 除零,人为的使程序崩溃

//

int i = 13;

int j = 0;

int m = i / j;

MessageBox(NULL, _T("Funcation completed"), _T("Test"), MB_OK);

return 0;

}

//
得到当前时间
//
wstring GetPresentTime()
{
SYSTEMTIME time;
GetLocalTime(&time);

wchar_t wszTime[128];
swprintf_s(wszTime, _T("%04d-%02d-%02d %02d-%02d-%02d-%03d"),
time.wYear, time.wMonth, time.wDay, time.wHour, time.wMinute,
time.wSecond, time.wMilliseconds);

return wstring(wszTime);
}

// 处理异常的回调函数
//
LONG CrashHandler(EXCEPTION_POINTERS *pException)
{
// 在这里添加处理程序崩溃情况的代码
//

// 这里以弹出一个对话框为例子
//
MessageBox(NULL, _T("Message from Catch handler"), _T("Test"), MB_OK);

// 以当前时间为文件名
//
wstring strDumpFileName = _T("C:\\") + GetPresentTime() +_T(".dmp");

// 创建Dump文件
//
CreateDumpFile(strDumpFileName.data(), pException);

return EXCEPTION_EXECUTE_HANDLER;
}

int _tmain(int argc, _TCHAR* argv[])
{
// 设置处理Unhandled Exception的回调函数
//

SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)CrashHandler);

__try
{
MessageBox(NULL, _T("Message from '__try' section"), _T("Test"),
MB_OK);

// 除零,人为的使程序崩溃
//
int i = 13;
int j = 0;
int m = i / j;
}
__except(CrashHandler(GetExceptionInformation()))
{
// 在这里添加处理程序崩溃情况的代码
//

// 这里以弹出一个对话框为例子
//
MessageBox(NULL, _T("Message from '__except' section"), _T("Test"),
MB_OK);
}

// 除零,人为的使程序崩溃
//
int i = 13;
int j = 0;
int m = i / j;

MessageBox(NULL, _T("Funcation completed"), _T("Test"), MB_OK);

return 0;
}


编译上面的代码,运行生成的EXE文件,可以看到在弹出上面提到的那一系列的对话框后,程序正常退出,没有崩溃。同时,在C盘下生成了2个Dump文件,文件名指出了发生异常的时刻。

上面的代码仅仅是为了说明怎样配合使用SEH和SetUnhandledExceptionFilter,所以except后的表达式和
SetUnhandledExceptionFilter中所设置的回调函数都使用了同一个函数CrashHandler。在实际的应用中可以根据不同的
需求而使用不同的函数。这个函数的参数必须是一个指向EXCEPTION_POINTERS的指针,返回值必须是这3个中的一
个:EXCEPTION_CONTINUE_SEARCH,EXCEPTION_CONTINUE_EXECUTION,EXCEPTION_EXECUTE_HANDLER。
这3个值的具体含义可以查阅MSDN或者我的那篇《让程序在崩溃时体面的退出之SEH》。

实际情况下,是不应该用同一个回调函数的。因为在except表达式中的函数是处理try块中的代码异常的;而用
SetUnhandledExceptionFilter设置的回调函数是用来处理代码中没有被捕捉到的异常的。对于未被捕捉到的异常,这个回调函数是不
知道异常发生的地方的,虽然可以通过异常代码知道异常的类型,但是由于不知道是什么状况引起的异常,所以没法做出相应的异常处理。一般情况下,这个回调函
数是应用程序崩溃前的最后一道防线,这个函数中的代码被执行完后,应用程序就会被终止。所以,大部分的应用程序,在这个函数里都是弹出一个发送错误报告的
对话框,来告诉用户程序发生异常,需要终止,可以把错误报告(一般是包括Dump文件和一些必要的文本信息)发送到指定地方帮助开发者来修改代码缺陷,以
提高软件质量。

使用上面的方法编写出的应用程序不会崩溃,并且在出现异常的时候会产生Dump文件。程序的使用者会获得非常良好的用户体验。如果再给应用程序添加上Log信息,配合上Dump文件,就可以很轻松的定位程序中的异常,帮助开发者快速的修复代码中的错误。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐