Рихтер Дж., Назар К. - Windows via C C++. Программирование на языке Visual C++ - 2009
.pdf848 Часть V. Структурная обработка исключений
После запуска программы Customized WER открывается следующее окно:
По щелчку кнопки Access Violation вызывается следующая функция:
void TriggerException() {
//провоцируем исключение, с которым связан блок finally,
//код в котором исполняется только при глобальной раскрутке
__try {
TCHAR* р = NULL;
*р = ТЕХТ('а');
}
__finally {
MessageBox(NULL, TEXT("Finally block is executed"), NULL, MB_OK);
}
}
Далее исполняется функция CustomUnhandledExceptionFilter — фильтр исключений блока try/except, защищающего входную функцию приложения:
int APIENTRY _tWinMain(HINSTANCE hInstExe, HINSTANCE, LPTSTR, int) {
int iReturn = 0;
//отключаем автоматическую отладку по запросу,
//которая могла быть включена ранее вызовом
//CustomUnhandledExceptionFilter EnableAutomaticJITDebug(FALSE);
//Защищаем код собственным фильтром исключений
__try {
DialogBox(hInstExe, MAKEINTRESOURCE(IDD_MAINDLG), NULL, Dlg_Proc);
}
__except(CustomUnhandledExceptionFilter(GetExceptionInformation())) { MessageBox(NULL, TEXT("Bye bye"), NULL, MB_0K);
Exit Process( GetExceptionCode());
}
return(iReturn);
}
Глава 26. Отчеты об ошибках и восстановление приложений.docx 849
Работа фильтра исключений управляется через главное окно приложения:
static BOOL s_bFirstTime = TRUE;
LONG WINAPI CustomUnhandledExceptionFilter(
struct _EXCEPTION_POINTERS* pExceptionlnfo) {
// После подключения отладчика и завершения отладки
//исполнение возобновляется с этого места.
//Если это так, приложение «молча» завершается if (s_bFirstTime)
s_bFirstTime = FALSE; else
ExitProcess(pExceptionInfo->ExceptionRecord->ExceptionCode);
//проверяем параметр завершения
if (!s.bCustom)
// позволяем Windows самой обработать исключение return(UnhandledExceptionFilter(pExceptionInfo));
//по умолчанию разрешаем глобальную раскрутку
LONG lReturn = EXCEPTION_EXECUTE_HANDLER;
//позволим пользователю выбрать между отладкой и закрытием приложений,
//если отладка по запросу не отключена в главном окне
int iChoice = IDCANCEL; if (s_bAllowJITDebug) {
iChoice = MessageBox(NULL,
TEXT("Click RETRY if you want to debug\nClick CANCEL to quit"), TEXT("The application must stop"), MB_RETRYCANCEL | MB_ICONHAND);
}
if (iChoice == IDRETRY) {
//включаем автоматической отладки для этого приложения
EnableAutomaticJITDebug(TRUE);
//просим Windows подключать по запросу отладчик по умолчанию lReturn = EXCEPTION_CONTINUE_SEARCH;
}else {
//приложение будет завершено
lReturn = EXCEPTION_EXECUTE_HANDLER;
// но сначала проверим, не требуется ли сначала создать отчет о сбое if (s_bGenerateReport)
GenerateWerReport(pExceptionInfo);
}
return(lReturn);
}
850 Часть V. Структурная обработка исключений
Если выбран переключатель Default, возвращается вывод функции UnhandledExceptionFilter (обработчика исключений по умолчанию в Windows). В этом случае открываются обычные окна и создается отчет о сбое, как описано в предыдущих разделах.
Если же установлен переключатель Custom, работой фильтра CustomUnhandledExceptionFilter управляют дополнительные параметры. Если выбрать Allow Debug, откроется такое диалоговое окно:
Если пользователь щелкнет Retry, нужно попробовать обхитрить Windows, чтобы начать отладку по запросу, поскольку возврата EXCEPTION_CONTINUESEARCH недостаточно: он просто означает, что исключение не обработано, и WER снова открывает стандартные окна, чтобы пользователь выбрал, что делать. Роль функции EnableAutomaticJITDebug заключается в том, чтобы сообщить WER, что выбранное подключение отладчика уже выполнено.
void EnableAutomaticJITDebug(BOOL bAutomaticDebug) {
// при необходимости создаем раздел реестра
const LPCTSTR szKeyName = TEXT("Software\\Microsoft\\Windows\\" + TEXT(Windows Error ReportingWDebugApplications");
HKEY hKey = NULL;
DWORD dwDisposition = 0; LSTATUS lResult = ERR0R_SUCCESS;
lResult = RegCreateKeyEx(HKEY_CURRENT_USER, szKeyName, 0, NULL, REG_0PTI0N_NON_V0LATILE, KEY_WRITE, NULL, &Mey, &dwDisposition);
if (lResult != ERROR_SUCCESS) {
MessageBox(NULL, TEXT("RegCreateKeyEx failed"), TEXT("EnableAutomaticJITDebug"), MB_OK | MB_CONHAND);
return;
}
// присваиваем правильное значение параметру реестра
DWORD dwValue = bAutomaticDebug ? 1 : 0; TCHAR szFullpathName[MAX_PATH];
GetModuleFileName(NULL, szFullpathName, _countof(szFullpathName)); LPTSTR pszExeName = _tcsrchr(szFullpathName, TEXT('W'));
if (pszExeName != NULL) { // пропускаем '\' pszExeName++;
Глава 26. Отчеты об ошибках и восстановление приложений.docx 851
// устанавливаем значение
lResult = RegSetValueEx(hKey, pszExeName, 0, REG_DWORD, (const BYTE*)&dwValue, sizeof(dwValue));
if (lResult != ERROR_SUCCESS) {
MessageBox(NULL, TEXT("RegSetValueExfailed"), TEXTCEnableAutomaticJITDebug"), MB_OK | MB_ICOMHAND);
return;
}
}
}
Ничего сложного в этом коде нет, он использует параметры реестра, описанные в разделе «Отладка по запросу» предыдущей главы. Заметьте, что при запуске приложения функция EnableAutomaticJITDebug вызывается с параметром FALSE для сброса параметра реестра в 0.
Если в главном окне программы выбран параметр Allow Debug, процесс завершается без уведомлений, как при щелчке кнопки Cancel: возвращается значение EXCEPTION_EXECUTE_HANDLER и глобальный блок except вызывает ExitProcess. Однако перед возвратом EXCEPTION_EXECUTE_HANDLER программа проверяет, не выбран ли параметр Generate A Report. Если это так, генерируется нестандартный отчет WER с помощью функции GenerateWerReport, в свою очередь вызывающей WerReport*-функции, описанное в этой главе выше:
LONG GenerateWerReport(struct _EXCEPTION_POINTERS* pExceptionInfo) {
// Default return value
LONG lResult = EXCEPTION_CONTINUE_SEARCH;
//Avoid stack problem because wri is a big structure static WER_REPORT_INFORMATION wri = { sizeof(wri) };
//Set the report details
StringCchCopyW(wri.wzFriendlyEventName, _countof(wri.wzFriendlyEventName),
L"Unexpected Error - 0x12345678");
StringCchCopyW(wri.wzApplicationName, _countof(wri.wzApplicationName),
L"Wintellect Applications Suite");
GetModuleFileNameW(NULL, (WCHAR*)&(wri.wzApplicationPath),
_countof(wri.wzApplicationPath));
StringCchCopyW(wri.wzDescription, _countof(wri.wzDescription),
L"This problem report is generated for testing purpose");
HREPORT hReport = NULL;
// Create a report and set additional information
__try { // V-- instead of the default APPCRASH_EVENT HRESULT hr = WerReportCreate(L"Unexpected Error",
WerReportApplicationCrash, &wri, &hReport);
852 Часть V. Структурная обработка исключений
if (FAILED(hr)) {
MessageBox(NULL, TEXT("WerReportCreate failed"), TEXT("GenerateWerReport"), MB_OK | MB_ICONHAND);
return(EXCEPTION_CONTINUE_SEARCH);
}
if (hReport == NULL) {
MessageBox(NULL, TEXT("WerReportCreate failed"), TEXT("GenerateWerReport"), MB_OK | MB_ICONHAND);
return(EXCEPTION_CONTINUE_SEARCH);
}
//Set more details important to help fix the problem WerReportSetParameter(hReport, WER_P0,
L"Application Name", L"26-CustomizedWER.exe"); WerReportSetParameter(hReport, WER_P1,
L"Application Version", L"5.0.0.0"); WerReportSetParameter(hReport, WER_P2,
L"Last Action", L"Server Request #12"); WerReportSetParameter(hReport, WER_P3,
L"Last Connected Server", L"http://www.wintellect.com");
//Add a dump file corresponding to the exception information WER_EXCEPTION_INFORMATION wei;
wei.bClientPointers = FALSE; |
// We are in the process where |
wei.pExceptionPointers = pExceptionInfo; |
// pExceptionInfo is valid |
hr = WerReportAddDump( |
|
hReport, GetCurrentProcess(), GetCurrentThread(), WerDumpTypeHeapDump, &wei, NULL, 0);
if (FAILED(hr)) {
MessageBox(NULL, TEXT("WerReportAddDump failed"), TEXT("GenerateWerReport"), MB_OK | MB_ICONHAND);
return(EXCEPTION_CONTINUE_SEARCH);
}
// Let memory blocks be visible from a mini-dump s_moreInfo1.dwCode = 0x1;
s_moreInfo1.dwValue = 0xDEADBEEF; s_moreInfo2.dwCode = 0x2; s_moreInfo2.dwValue = 0x0BADBEEF;
hr = WerRegisterMemoryBlock(&s_moreInfo1, sizeof(s_moreInfo1)); if (hr != S_OK) { // Don't want S_FALSE
MessageBox(NULL, TEXT("First WerRegisterMemoryBlock failed"), TEXT("GenerateWerReport"), MB_OK | MB_ICONHAND);
return(EXCEPTION_CONTINUE_SEARCH);
}
hr = WerRegisterMemoryBlock(&s_moreInfo2, sizeof(s_moreInfo2));
Глава 26. Отчеты об ошибках и восстановление приложений.docx 853
if (hr != S_OK) { // Don't want S_FALSE
MessageBox(NULL, TEXT("Second WerRegisterMemoryBlock failed"), TEXT("GenerateWerReport"), MB_OK | MB_ICONHAND);
return(EXCEPTION_CONTINUE_SEARCH);
}
// Add more files to this particular report wchar_t wszFilename[] = L"MoreData.txt";
char textData[] = "Contains more information about the execution \r\n\ context when the problem occurred. The goal is to \r\n\
help figure out the root cause of the issue.";
// Note that error checking is removed for readability
HANDLE hFile = CreateFileW(wszFilename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
DWORD dwByteWritten = 0;
WriteFile(hFile, (BYTE*)textData, sizeof(textData), &dwByteWritten, NULL);
CloseHandle(hFile);
hr = WerReportAddFile(hReport, wszFilename, WerFileTypeOther, WER_FILE_ANONYMOUS_DATA);
if (FAILED(hr)) {
MessageBox(NULL, TEXT("WerReportAddFile failed"), TEXT("GenerateWerReport"), MB_OK | MB_ICONHAND);
return(EXCEPTION_CONTINUE_SEARCH);
}
// It is also possible to use WerRegisterFile
char textRegisteredData[] = "Contains more information about the \ execution \r\ncontext when the problem occurred. The goal is to \r\n\ help figure out the root cause of the issue.";
// Note that error checking is removed for readability
hFile = CreateFileW(L"RegisteredData1.txt", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
dwByteWritten = 0;
WriteFile(hFile, (BYTE*)textRegisteredData, sizeof(textRegisteredData), &dwByteWritten, NULL);
CloseHandle(hFile);
hr = WerRegisterFile(L"RegisteredData1.txt", WerRegFileTypeOther, WER_FILE_ANONYMOUS_DATA);
if (FAILED(hr)) {
MessageBox(NULL, TEXT("First WerRegisterFile failed"), TEXT("GenerateWerReport"), MB_OK | MB_ICONHAND);
return(EXCEPTION_CONTINUE_SEARCH);
}
hFile = CreateFileW(L"RegisteredData2.txt", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
854 Часть V. Структурная обработка исключений
dwByteWritten = 0;
WriteFile(hFile, (BYTE*)textRegisteredData, sizeof(textRegisteredData), &dwByteWritten, NULL);
CloseHandle(hFile);
hr = WerRegisterFile(L"RegisteredData2.txt", WerRegFileTypeOther, WER_FILE_DELETE_WHEN_DONE); // File is deleted after WerReportSubmit
if (FAILED(hr)) {
MessageBox(NULL, TEXT("Second WerRegisterFile failed"), TEXT("GenerateWerReport"), MB_OK | MB_ICONHAND);
return(EXCEPTION_CONTINUE_SEARCH);
}
//Submit the report WER_SUBMIT_RESULT wsr; DWORD submitOptions = WER_SUBMIT_QUEUE |
WER_SUBMIT_OUTOFPROCESS | WER_SUBMIT_NO_CLOSE_UI; // Don't show any UI
hr = WerReportSubmit(hReport, WerConsentApproved, submitOptions, &wsr); if (FAILED(hr)) {
MessageBox(NULL, TEXT("WerReportSubmit failed"), TEXT("GenerateWerReport"), MB_OK | MB_ICONHAND);
return(EXCEPTION_CONTINUE_SEARCH);
}
//The submission was successful, but we might need to check the result switch(wsr)
{
case WerReportQueued:
case WerReportUploaded: // To exit the process lResult = EXCEPTION_EXECUTE_HANDLER; break;
case WerReportDebug: // To end up in the debugger lResult = EXCEPTION_CONTINUE_SEARCH;
break;
default: // Let the OS handle the exception lResult = EXCEPTION_CONTINUE_SEARCH; break;
}
// In our case, we always exit the process after the report generation lResult = EXCEPTION_EXECUTE_HANDLER;
}
__finally {
Глава 26. Отчеты об ошибках и восстановление приложений.docx 855
// Don't forget to close the report handle if (hReport != NULL) {
WerReportCloseHandle(hReport); hReport = NULL;
}
}
return(lResult);
}
Автоматический перезапуск и восстановление приложений
При возникновении в приложении критического сбоя, WER может автоматически закрыть, а затем перезапустить его. Поддержка этой функции — непременный ат-
рибут стандартных программ Windows Vista (Explorer, Internet Explorer, RegEdit,
игр и пр.). Более того, WER позволяет сохранить важные данные перед завершением приложения.
Автоматический перезапуск приложения
Приложение, поддерживающее автоматический перезапуск, должно зарегистрироваться у WER вызовом следующей функции:
HRESULT RegisterApplicationRestart(
PCWSTR pwzCommandline,
DWORD dwFlags);
Ее параметр pwzCommandLine — это командная строка (в формате Unicode), которую WER должна использовать для перезапуска приложения. Если ваша программа не использует специальных аргументов командной строки, уведомляющих ее о перезапуске, передавайте в этом параметре NULL. Если передать 0 в параметре dwFlags, приложение будет перезапускаться каждый раз, когда WER обнаружит в нем критический сбой. Тонкую настройку условий, требующих перезапуска приложения, можно выполнить с помощью комбинации значений, перечисленных в табл. 26-11.
Табл. 26-11. Флаги для настройки перезапуска приложений
Флаг |
Описание |
RESTART_NO_CRASH = 1 |
Запрещает перезапуск приложения после его краха |
RESTART_NO_HANG = 2 |
Запрещает перезапуск приложения после его зависания |
RESTART_NO_PATCH = 4 |
Запрещает перезапуск приложения после установки обновле- |
|
ний |
RESTART_NO_REBOOT = 8 |
Запрещает перезапуск приложения после перезагрузки системы |
|
вследствие установки обновления |
Глава 26. Отчеты об ошибках и восстановление приложений.docx 857
Рис. 26-8. Уведомление пользователя о подготовке к восстановлению приложения
Функция pfnRecoveryCallback должна уведомлять WER о своем исполнении, вызывая функцию ApplicationRecoverylnProgress по истечении интервала, заданного параметром dwPinglnterval (в мс). Если своевременный вызов ApplicationRecoverylnProgress не последует, WER закрывает процесс. Функция ApplicationRecoverylnProgress принимает указатель на BOOL-значение, уведомляющее о щелчке кнопки Cancel пользователем (см. рис. 26-8). Завершаясь, выполняющая восстановление функция должна вызвать ApplicationRecoveryFinished, чтобы уведомить WER об успехе или неудаче восстановления.
Вот пример функции обратного вызова, выполняющей восстановление приложения:
DWORD WINAPI ApplicationRecoveryCallback(PVOID pvParameter) {
DWORD dwReturn = 0;
BOOL bCancelled = FALSE; while (!bCancelled) {
//индикатор хода операции
ApplicationRecoveryInProgress(&bCancelled);
//проверяем, не отменил ли пользователь операцию if (bCancelled) {
//пользователь щелкнул кнопку Cancel
//уведомляем о неудаче восстановления
ApplicationRecoveryFlnished(FALSE);
}else {
//сохраняем порции данных, необходимые для восстановления
if (MoreInformationToSave()) {
//запись порции данных занимает меньше времени, чем задержка,
//установленная параметром dwPingInterval функции
//RegisterApplicationRecoveryCallback