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

在我的上篇文章《 让程序在崩溃时体面的退出之SEH+Dump文件》我介绍了怎样用SEH加上Dump文件来避免程序的崩溃并在程序崩溃时创建Dump文件来帮助定位出现异常的代码行。可是只有try/except块中try块中的代码出现异常才能被捕捉到,try块外面的代码出现异常,程序照样会崩溃。
        下面用《 让程序在崩溃时体面的退出之SEH+Dump文件》文中的代码为例子来说明。
  1. // 创建Dump文件 
  2. //  
  3. void CreateDumpFile(LPCWSTR lpstrDumpFilePathName, EXCEPTION_POINTERS *pException) 
  4.     // 创建Dump文件 
  5.     // 
  6.     HANDLE hDumpFile = CreateFile(lpstrDumpFilePathName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 
  7.  
  8.     // Dump信息 
  9.     // 
  10.     MINIDUMP_EXCEPTION_INFORMATION dumpInfo; 
  11.     dumpInfo.ExceptionPointers = pException; 
  12.     dumpInfo.ThreadId = GetCurrentThreadId(); 
  13.     dumpInfo.ClientPointers = TRUE; 
  14.  
  15.     // 写入Dump文件内容 
  16.     // 
  17. MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, MiniDumpNormal, &dumpInfo, NULL, NULL);
  18.  
  19.     CloseHandle(hDumpFile); 
  20.  
  21. // 作为except块中表达式的函数 
  22. // 
  23. LONG CrashHandler(EXCEPTION_POINTERS *pException) 
  24. {    
  25.     // 在这里添加处理程序崩溃情况的代码 
  26.     // 
  27.  
  28.     // 这里以弹出一个对话框为例子 
  29.     // 
  30.     MessageBox(NULL, _T("Message from Catch handler"), _T("Test"), MB_OK); 
  31.  
  32.     // 创建Dump文件 
  33.     // 
  34.     CreateDumpFile(_T("C:\\Test.dmp"), pException); 
  35.  
  36.     return EXCEPTION_EXECUTE_HANDLER; 
  37.  
  38. int _tmain(int argc, _TCHAR* argv[]) 
  39.     __try 
  40.     { 
  41.         MessageBox(NULL, _T("Message from '__try' section"), _T("Test"), MB_OK); 
  42.  
  43.         // 除零,人为的使程序崩溃 
  44.         // 
  45.         int i = 13; 
  46.         int j = 0; 
  47.         int m = i / j; 
  48.     } 
  49.     // 捕捉到让程序崩溃的异常时创建Dump文件 
  50.     // 
  51.     __except(CrashHandler(GetExceptionInformation())) 
  52.     { 
  53.         // 这里以弹出一个对话框为例子 
  54.         // 
  55.         MessageBox(NULL, _T("Message from '__except' section"), _T("Test"), MB_OK); 
  56.     } 
  57.  
  58.     MessageBox(NULL, _T("Funcation completed"), _T("Test"), MB_OK); 
  59.  
  60.     return 0; 

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

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

  1. int _tmain(int argc, _TCHAR* argv[]) 
  2.     __try 
  3.     { 
  4.         MessageBox(NULL, _T("Message from '__try' section"), _T("Test"), MB_OK); 
  5.  
  6.         // 除零,人为的使程序崩溃 
  7.         // 
  8.         int i = 13; 
  9.         int j = 0; 
  10.         int m = i / j; 
  11.     } 
  12.     // 捕捉到让程序崩溃的异常时创建Dump文件 
  13.     // 
  14.     __except(CrashHandler(GetExceptionInformation())) 
  15.     { 
  16.         // 这里以弹出一个对话框为例子 
  17.         // 
  18.         MessageBox(NULL, _T("Message from '__except' section"), _T("Test"), MB_OK); 
  19.     } 
  20.  
  21.     // 除零,人为的使程序崩溃 
  22.     // 
  23.     int i = 13; 
  24.     int j = 0; 
  25.     int m = i / j; 
  26.  
  27.     MessageBox(NULL, _T("Funcation completed"), _T("Test"), MB_OK); 
  28.  
  29.     return 0; 

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

  1. // 得到当前时间 
  2. // 
  3. wstring GetPresentTime() 
  4.      SYSTEMTIME time; 
  5.      GetLocalTime(&time);  
  6.  
  7.      wchar_t wszTime[128]; 
  8.      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); 
  9.  
  10.      return wstring(wszTime); 
  11.  
  12. // 处理异常的回调函数 
  13. // 
  14. LONG CrashHandler(EXCEPTION_POINTERS *pException) 
  15. {    
  16.     // 在这里添加处理程序崩溃情况的代码 
  17.     // 
  18.  
  19.     // 这里以弹出一个对话框为例子 
  20.     // 
  21.     MessageBox(NULL, _T("Message from Catch handler"), _T("Test"), MB_OK); 
  22.  
  23.     // 以当前时间为文件名 
  24.     // 
  25.     wstring strDumpFileName = _T("C:\\") + GetPresentTime() +_T(".dmp"); 
  26.  
  27.     // 创建Dump文件 
  28.     // 
  29.     CreateDumpFile(strDumpFileName.data(), pException); 
  30.  
  31.     return EXCEPTION_EXECUTE_HANDLER; 
  32.  
  33. int _tmain(int argc, _TCHAR* argv[]) 
  34.     // 设置处理Unhandled Exception的回调函数 
  35.     //  
  36.     SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)CrashHandler);  
  37.  
  38.     __try 
  39.     { 
  40.         MessageBox(NULL, _T("Message from '__try' section"), _T("Test"), MB_OK); 
  41.  
  42.         // 除零,人为的使程序崩溃 
  43.         // 
  44.         int i = 13; 
  45.         int j = 0; 
  46.         int m = i / j; 
  47.     } 
  48.     __except(CrashHandler(GetExceptionInformation())) 
  49.     { 
  50.         // 在这里添加处理程序崩溃情况的代码 
  51.         // 
  52.  
  53.         // 这里以弹出一个对话框为例子 
  54.         // 
  55.         MessageBox(NULL, _T("Message from '__except' section"), _T("Test"), MB_OK); 
  56.     } 
  57.  
  58.     // 除零,人为的使程序崩溃 
  59.     // 
  60.     int i = 13; 
  61.     int j = 0; 
  62.     int m = i / j; 
  63.  
  64.     MessageBox(NULL, _T("Funcation completed"), _T("Test"), MB_OK); 
  65.  
  66.     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文件,就可以很轻松的定位程序中的异常,帮助开发者快速的修复代码中的错误。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值