Как правильно использовать FormatMessage () в C ++?

90

Без :

  • MFC
  • ATL

Как я могу FormatMessage()получить текст ошибки для a 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 напрямую.

Shog9
источник
8
Осторожно: этот код использует hResult вместо кода ошибки Win32: это разные вещи! Вы можете получить текст совершенно другой ошибки, чем та, которая действительно произошла.
Андрей Белогорцев
1
Отличная точка, @Andrei - и в самом деле, даже если ошибка есть ошибка Win32, эта процедура будет успешной , только если это система ошибка - это надежный механизм обработки ошибок будет необходим знать источник ошибки, проанализируйте код перед вызовом FormatMessage и, возможно, вместо этого запросить другие источники.
Shog9
1
@AndreiBelogortseff Как я могу узнать, что использовать в каждом случае? Например, RegCreateKeyExвозвращает LONG. В его документации сказано, что я могу использовать его FormatMessageдля получения ошибки, но мне нужно передать файл LONGв файл HRESULT.
csl
FormatMessage () принимает DWORD, @csl, целое число без знака, которое считается допустимым кодом ошибки. Не все возвращаемые значения - или HRESULTS в этом отношении - будут допустимыми кодами ошибок; система предполагает, что вы подтвердили, что это было до вызова функции. Документы для 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 (), allocatedMessage). В этом случае это то же самое, что и вызов LocalFree в памяти.

Обновление
Я обнаружил, что LocalFreeэто версия SDK 10.0.10240.0 (строка 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?

Класс Скелет
источник
5

Начиная с c ++ 11, вы можете использовать стандартную библиотеку вместо FormatMessage:

#include <system_error>

std::string message = std::system_category().message(hr)
Хрониальный
источник
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или ARRAYSIZEиначе sizeof(buffer) / sizeof(buffer[0])вместо sizeof.
ThFabba 07
2

Как указано в других ответах:

  • FormatMessageберет DWORDрезультат не a 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 ++, который я написал, в отличие от Microsoft ErrorExit (), но немного изменен, чтобы избежать использования всех макросов и использовать Unicode. Идея здесь в том, чтобы избежать ненужных приведений и ошибок. Я не мог избежать всех кастингов 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);
}

источник