Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Рихтер Дж., Назар К. - Windows via C C++. Программирование на языке Visual C++ - 2009

.pdf
Скачиваний:
6269
Добавлен:
13.08.2013
Размер:
31.38 Mб
Скачать

848 Часть 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

Запрещает перезапуск приложения после перезагрузки системы

 

вследствие установки обновления

856 Часть V. Структурная обработка исключений

Последние два флага выглядят странными в контексте обработки исключений, но поддержка перезапуска приложений входит в более общий API под названием Restart Manager (подробнее о нем см. в статье MSDN по ссылке http://msdn2.microsoft.com/en-us/library/aa373651.aspx).

После вызова функции RegisterApplicationRestart при возникновении в процесс критического сбоя, который обрабатывает WER, приложение перезапускается и открывается окно, показанное на рис. 26-7.

Рис. 26-7. Уведомление пользователя о перезапуске приложения

Чтобы предотвратить многократный перезапуск сбойного приложения, WER, прежде чем перезапустить процесс, проверяет, проработал ли он до перезапуска хотя бы 60 секунд.

Примечание. Чтобы запретить WER перезапуск приложения, вызовите следующую функцию:

HRESULT UnregisterApplicationRestart();

Поддержка восстановления приложений

Процесс может зарегистрировать функцию обратного вызова, которую WER может вызвать при аварийном завершении этого процесса. Эта функция может сохранять нужные данные или сведения о состоянии. Для регистрации функции обратного вызова используют следующую функцию:

HRESULT RegisterApplicatiortRecoveryCallback(

APPLICATION_RECOVERY_CALLBACK pfnRecoveryCallback,

PVOID pvParameter,

DWORD

dwPinglnterval,

 

DWORD

dwFlags);

// зарезервирован; должен быть равен 0

Параметр pfnRecoveryCallback должен указывать на функцию со следующей сигнатурой:

DWORD WINAPI ApplicationRecoveryCallback(PVOID pvParameter);

WER осуществляет обратный вызов с параметром pvParameter, который вы передали при вызове RegisterApplicationRecoveryCallback. При этом WER откры-

вает окно, показанное на рис. 26-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

Соседние файлы в предмете Программирование на C++