Как правильно использовать 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 80

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