Як мені правильно використовувати FormatMessage () у C ++?


90

Без :

  • MFC
  • ATL

Як я можу FormatMessage()отримати текст помилки для HRESULT?

 HRESULT hresult = application.CreateInstance("Excel.Application");

 if (FAILED(hresult))
 {
     // what should i put here to obtain a human-readable
     // description of the error?
     exit (hresult);
 }

Відповіді:


134

Ось правильний спосіб повернути повідомлення про помилку із системи для HRESULT(з іменем hresult у цьому випадку, або ви можете замінити його на GetLastError()):

LPTSTR errorText = NULL;

FormatMessage(
   // use system message tables to retrieve error text
   FORMAT_MESSAGE_FROM_SYSTEM
   // allocate buffer on local heap for error text
   |FORMAT_MESSAGE_ALLOCATE_BUFFER
   // Important! will fail otherwise, since we're not 
   // (and CANNOT) pass insertion parameters
   |FORMAT_MESSAGE_IGNORE_INSERTS,  
   NULL,    // unused with FORMAT_MESSAGE_FROM_SYSTEM
   hresult,
   MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
   (LPTSTR)&errorText,  // output 
   0, // minimum size for output buffer
   NULL);   // arguments - see note 
   
if ( NULL != errorText )
{
   // ... do something with the string `errorText` - log it, display it to the user, etc.

   // release memory allocated by FormatMessage()
   LocalFree(errorText);
   errorText = NULL;
}

Ключова різниця між цим та відповіддю Девіда Ханака - використання FORMAT_MESSAGE_IGNORE_INSERTSпрапора. MSDN трохи незрозумілий щодо того, як слід використовувати вставки, але Реймонд Чен зазначає, що ви ніколи не повинні використовувати їх під час отримання системного повідомлення, оскільки ви ніяк не можете знати, яких вставок система очікує.

FWIW, якщо ви використовуєте Visual C ++, ви можете полегшити своє життя, використовуючи _com_errorклас:

{
   _com_error error(hresult);
   LPCTSTR errorText = error.ErrorMessage();
   
   // do something with the error...

   //automatic cleanup when error goes out of scope
}

Наскільки мені відомо, він не є частиною MFC або ATL.


8
Обережно: цей код використовує hResult замість коду помилки Win32: це різні речі! Ви можете отримати текст зовсім іншої помилки, ніж помилка, яка насправді сталася.
Андрій Белогорцев,

1
Чудовий момент, @Andrei - і справді, навіть якщо помилка є помилкою Win32, ця процедура вдасться лише в тому випадку, якщо це системна помилка - надійний механізм обробки помилок повинен знати про джерело помилки, вивчити код перед викликом FormatMessage і, можливо, замість цього запитуйте інші джерела.
Shog9

1
@AndreiBelogortseff Як я можу знати, що використовувати в кожному випадку? Наприклад, RegCreateKeyExповертає a LONG. Його документи говорять , що я можу використовувати , FormatMessageщоб отримати помилку, але я повинен закидати LONGв HRESULT.
csl

FormatMessage () приймає DWORD, @csl, ціле число без підпису, яке вважається дійсним кодом помилки. Не всі повернені значення - або РЕЗУЛЬТАТИ з цього приводу - будуть дійсними кодами помилок; система припускає, що ви підтвердили, що це було до виклику функції. Документи для RegCreateKeyEx повинні вказати, коли повернене значення може бути інтерпретоване як помилка ... Виконайте цю перевірку спочатку , а вже потім викликайте FormatMessage.
Shog9

1
MSDN фактично надає свою версію приблизно такого ж коду.
ahmd0

14

Майте на увазі, що ви не можете зробити наступне:

{
   LPCTSTR errorText = _com_error(hresult).ErrorMessage();

   // do something with the error...

   //automatic cleanup when error goes out of scope
}

Коли клас створюється та знищується в стеку, залишаючи errorText вказувати на недійсне розташування. У більшості випадків це місце все ще міститиме рядок помилки, але ця ймовірність швидко падає під час написання різьбових програм.

Тому завжди робіть це так, як відповів Shog9 вище:

{
   _com_error error(hresult);
   LPCTSTR errorText = error.ErrorMessage();

   // do something with the error...

   //automatic cleanup when error goes out of scope
}

7
_com_errorОб'єкт створюється на стеку в обох ваших прикладах. Термін, який ви шукаєте, є тимчасовим . У першому прикладі об'єкт є тимчасовим, який знищується в кінці виписки.
Роб Кеннеді,

Так, мав на увазі це. Але я сподіваюся, що більшість людей зможуть принаймні зрозуміти це з коду. Технічно тимчасові знищуються не в кінці виписки, а в кінці точки послідовності. (що в цьому прикладі те саме, що це просто розщеплення волосся.)
Маріус

1
Якщо ви хочете зробити це безпечним (можливо, не дуже ефективним ), ви можете зробити це в C ++:std::wstring strErrorText = _com_error(hresult).ErrorMessage();
ahmd0

11

Спробуйте це:

void PrintLastError (const char *msg /* = "Error occurred" */) {
        DWORD errCode = GetLastError();
        char *err;
        if (!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
                           NULL,
                           errCode,
                           MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // default language
                           (LPTSTR) &err,
                           0,
                           NULL))
            return;

        static char buffer[1024];
        _snprintf(buffer, sizeof(buffer), "ERROR: %s: %s\n", msg, err);
        OutputDebugString(buffer); // or otherwise log it
        LocalFree(err);
}

void HandleLastError (hresult)?
Аарон

1
Звичайно, ви можете зробити ці пристосування самостійно.
oefe

@Atklin: Якщо ви хочете використовувати hresult з параметра, вам, очевидно, не потрібен перший рядок (GetLastError ()).
Девід Ханак

4
GetLastError не повертає HResult. Він повертає код помилки Win32. Може віддавати перевагу імені PrintLastError, оскільки це насправді нічого не обробляє . І обов’язково використовуйте FORMAT_MESSAGE_IGNORE_INSERTS.
Роб Кеннеді

Дякуємо за допомогу, хлопці :) - дуже вдячний
Аарон

5

Це більше доповнення до більшості відповідей, але замість використання LocalFree(errorText)використовуйте HeapFreeфункцію:

::HeapFree(::GetProcessHeap(), NULL, errorText);

З сайту MSDN :

Windows 10 :
LocalFree відсутній у сучасному SDK, тому його не можна використовувати для звільнення буфера результатів. Натомість використовуйте HeapFree (GetProcessHeap (), alloMessage). У цьому випадку це те саме, що викликати LocalFree в пам’яті.

Оновлення, яке
я виявив, LocalFreeє у версії 10.0.10240.0 SDK (рядок 1108 у WinBase.h). Однак попередження все ще існує у посиланні вище.

#pragma region Desktop Family or OneCore Family
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM)

WINBASEAPI
_Success_(return==0)
_Ret_maybenull_
HLOCAL
WINAPI
LocalFree(
    _Frees_ptr_opt_ HLOCAL hMem
    );

#endif /* WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM) */
#pragma endregion

Оновлення 2
Я також пропоную використовувати FORMAT_MESSAGE_MAX_WIDTH_MASKпрапор для впорядкування розривів рядків у системних повідомленнях.

З сайту MSDN :

FORMAT_MESSAGE_MAX_WIDTH_MASK
Функція ігнорує регулярні розриви рядків у тексті визначення повідомлення. Функція зберігає жорстко закодовані розриви рядків у тексті визначення повідомлення у вихідному буфері. Функція не генерує нових розривів рядків.

Оновлення 3
Здається, є 2 конкретні коди системних помилок, які не повертають повне повідомлення, використовуючи рекомендований підхід:

Чому FormatMessage створює лише часткові повідомлення для системних помилок ERROR_SYSTEM_PROCESS_TERMINATED та ERROR_UNHANDLED_EXCEPTION?


4

Ось версія функції Девіда, яка обробляє Unicode

void HandleLastError(const TCHAR *msg /* = "Error occured" */) {
    DWORD errCode = GetLastError();
    TCHAR *err;
    if (!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
                       NULL,
                       errCode,
                       MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // default language
                       (LPTSTR) &err,
                       0,
                       NULL))
        return;

    //TRACE("ERROR: %s: %s", msg, err);
    TCHAR buffer[1024];
    _sntprintf_s(buffer, sizeof(buffer), _T("ERROR: %s: %s\n"), msg, err);
    OutputDebugString(buffer);
    LocalFree(err);

}


1
Зверніть увагу, що ви не передаєте правильний розмір буфера _sntprintf_sу випадку UNICODE. Функція приймає кількість символів, тому ви хочете _countofабо ARRAYSIZEaka sizeof(buffer) / sizeof(buffer[0])замість символу sizeof.
ThFabba

4

Оскільки c ++ 11, ви можете використовувати стандартну бібліотеку замість FormatMessage:

#include <system_error>

std::string message = std::system_category().message(hr)

2

Як зазначено в інших відповідях:

  • FormatMessageприймає DWORDрезультат, а не HRESULT(як правило GetLastError()).
  • LocalFree потрібен для звільнення пам'яті, виділеної FormatMessage

Я взяв вищезазначені моменти і додав ще кілька для своєї відповіді:

  • Оберніть FormatMessageв клас, щоб виділити та звільнити пам’ять за потреби
  • Використовуйте перевантаження оператора (наприклад, operator LPTSTR() const { return ...; }щоб ваш клас міг використовуватися як рядок
class CFormatMessage
{
public:
    CFormatMessage(DWORD dwMessageId,
                   DWORD dwLanguageId = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT)) :
        m_text(NULL)
    {
        Assign(dwMessageId, dwLanguageId);
    }

    ~CFormatMessage()
    {
        Clear();
    }

    void Clear()
    {
        if (m_text)
        {
            LocalFree(m_text);
            m_text = NULL;
        }
    }

    void Assign(DWORD dwMessageId,
                DWORD dwLanguageId = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT))
    {
        Clear();
        DWORD dwFlags = FORMAT_MESSAGE_FROM_SYSTEM
            | FORMAT_MESSAGE_ALLOCATE_BUFFER
            | FORMAT_MESSAGE_IGNORE_INSERTS,
        FormatMessage(
            dwFlags,
            NULL,
            dwMessageId,
            dwLanguageId,
            (LPTSTR) &m_text,
            0,
            NULL);
    }

    LPTSTR text() const { return m_text; }
    operator LPTSTR() const { return text(); }

protected:
    LPTSTR m_text;

};

Знайдіть більш повну версію вищевказаного коду тут: https://github.com/stephenquan/FormatMessage

У наведеному вище класі використання просто:

    std::wcout << (LPTSTR) CFormatMessage(GetLastError()) << L"\n";

0

Нижче наведений код - це еквівалент C ++, який я виписав на відміну від ErrorExit () від Microsoft, але трохи змінений, щоб уникнути всіх макросів та використовувати юнікод. Ідея тут полягає в тому, щоб уникнути непотрібних закидів та мальлоків. Я не зміг уникнути всіх акторів C, але це найкраще, що я міг зібрати. Що стосується FormatMessageW (), який вимагає призначення вказівника за допомогою функції форматування та ідентифікатора помилки з GetLastError (). Вказівник після static_cast можна використовувати як звичайний вказівник wchar_t.

#include <string>
#include <windows.h>

void __declspec(noreturn) error_exit(const std::wstring FunctionName)
{
    // Retrieve the system error message for the last-error code
    const DWORD ERROR_ID = GetLastError();
    void* MsgBuffer = nullptr;
    LCID lcid;
    GetLocaleInfoEx(L"en-US", LOCALE_RETURN_NUMBER | LOCALE_ILANGUAGE, (wchar_t*)&lcid, sizeof(lcid));

    //get error message and attach it to Msgbuffer
    FormatMessageW(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL, ERROR_ID, lcid, (wchar_t*)&MsgBuffer, 0, NULL);
    //concatonate string to DisplayBuffer
    const std::wstring DisplayBuffer = FunctionName + L" failed with error " + std::to_wstring(ERROR_ID) + L": " + static_cast<wchar_t*>(MsgBuffer);

    // Display the error message and exit the process
    MessageBoxExW(NULL, DisplayBuffer.c_str(), L"Error", MB_ICONERROR | MB_OK, static_cast<WORD>(lcid));

    ExitProcess(ERROR_ID);
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.