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

без:

  • 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);
 }

6 ответов


вот правильный способ получить сообщение об ошибке из системы для 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 напрямую, насколько мне известно.


имейте в виду, что вы не можете сделать следующее:

{
   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
}

попробуйте это:

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);
}

вот версия функции Дэвида, которая обрабатывает 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);

}


это больше дополнение к большинству ответов, но вместо того, чтобы использовать LocalFree(errorText) использовать :

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

С сайта MSDN:

Windows 10:
LocalFree не находится в современном SDK, поэтому его нельзя использовать для освобождения буфера результатов. Вместо этого используйте HeapFree (GetProcessHeap(), allocatedMessage). В этом случае это то же самое, что и вызов LocalFree on память.

обновление
Я нашел это LocalFree находится в версии 10.0.10240.0 SDK (строка 1108 в WinBase.ч.) Однако предупреждение по-прежнему существует в приведенной выше ссылке.

#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?


код ниже-это код, эквивалентный C++, который я написал в отличие от Microsoft ErrorExit () но слегка изменен, чтобы избежать всех макросов и использовать unicode. Идея здесь состоит в том, чтобы избежать ненужных слепков и маллоков. Я не мог избежать всех бросков C, но это лучшее, что я мог собрать. Относится к FormatMessageW (), который требует, чтобы указатель был выделен функцией format и идентификатором ошибки из 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);
}