Как правильно использовать 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);
 }
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
91
0
59 782
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

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

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

Aaron 18.01.2009 20:05

Конечно, вы можете сами внести эти изменения.

oefe 18.01.2009 20:12

@Atklin: если вы хотите использовать hresult из параметра, вам, очевидно, не нужна первая строка (GetLastError ()).

David Hanak 18.01.2009 20:28

GetLastError не возвращает HResult. Он возвращает код ошибки Win32. Вы можете предпочесть имя PrintLastError, поскольку на самом деле это ничего не ручка. И обязательно используйте FORMAT_MESSAGE_IGNORE_INSERTS.

Rob Kennedy 18.01.2009 20:57

Спасибо за вашу помощь, ребята :) - очень признателен

Aaron 18.01.2009 21:05

OutputDebugString по умолчанию преобразуется в OutputDebugStringW, если включен юникод. Я добавлю обновленную версию функции в свой ответ.

Oleg Zhylin 23.02.2015 22:35
Ответ принят как подходящий

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

Осторожно: этот код использует hResult вместо кода ошибки Win32: это разные вещи! Вы можете получить текст совершенно иной ошибки, чем та, которая действительно произошла.

Andrei Belogortseff 30.10.2014 02:00

Отличный момент, @Andrei - и действительно, даже если ошибка является является ошибкой Win32, эта процедура будет успешной только в том случае, если это ошибка система - надежный механизм обработки ошибок должен знать источник ошибки, изучить код перед вызовом FormatMessage и, возможно, вместо этого запросить другие источники.

Shog9 30.10.2014 04:14

@AndreiBelogortseff Как я могу узнать, что использовать в каждом конкретном случае? Например, RegCreateKeyEx возвращает LONG. В его документации говорится, что я могу использовать FormatMessage для извлечения ошибки, но мне нужно преобразовать LONG в HRESULT.

csl 18.06.2015 09:20

FormatMessage () принимает DWORD, @csl, целое число без знака, равное предполагается, как допустимый код ошибки. Не все возвращаемые значения - или HRESULTS в этом отношении - будут допустимыми кодами ошибок; система предполагает, что вы проверили это до вызова функции. Документы для RegCreateKeyEx должны указывать, когда возвращаемое значение может быть интерпретировано как ошибка ... Выполните эту проверку первый и только затем вызовите FormatMessage.

Shog9 18.06.2015 19:59

Связано: при отладке с помощью Visual Studio вы можете добавить следующие часы, чтобы получить ту же информацию: @err,hr. Обратите внимание, что при этом всегда оценивается последний код ошибки, даже если он не содержит достоверной информации. Вам нужно будет обратиться к справочной документации, чтобы узнать, когда значение действительно.

IInspectable 23.07.2015 18:15

Почему сравнение NULL идет в обратном направлении от того, что обычно (var != constant)?

Yet Another User 31.10.2016 01:35

У меня появилась привычка писать программы для Windows, когда компилятор не предупреждал вас, если вы ошиблись с оператором @Yet. См .: stackoverflow.com/questions/370366/…

Shog9 05.11.2016 00:31

На самом деле MSDN теперь предоставляет их версия вроде того же кода.

ahmd0 27.12.2018 20:54

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

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

Объект _com_error создается в стеке в оба ваших примеров. Вы ищете термин временный. В первом примере объект является временным, который уничтожается в конце оператора.

Rob Kennedy 17.09.2009 02:27

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

Marius 18.09.2009 20:40

Если вы хотите сделать это безопасный (возможно, не очень эффективный), вы можете сделать это на C++: std::wstring strErrorText = _com_error(hresult).ErrorMessage();

ahmd0 27.12.2018 21:22

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

}

Обратите внимание, что вы не передаете правильный размер буфера в _sntprintf_s в случае UNICODE. Функция принимает количество символов, поэтому вам нужен _countof или ARRAYSIZE, также известный как sizeof(buffer) / sizeof(buffer[0]), вместо sizeof.

ThFabba 07.07.2019 11:01

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

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

С сайта MSDN:

Windows 10:
LocalFree is not in the modern SDK, so it cannot be used to free the result buffer. Instead, use HeapFree (GetProcessHeap(), allocatedMessage). In this case, this is the same as calling LocalFree on memory.

Обновлять
Я обнаружил, что 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
The function ignores regular line breaks in the message definition text. The function stores hard-coded line breaks in the message definition text into the output buffer. The function generates no new line breaks.

Обновление 3
Похоже, есть 2 конкретных кода системных ошибок, которые не возвращают полное сообщение с использованием рекомендуемого подхода:

Почему FormatMessage создает только частичные сообщения для системных ошибок ERROR_SYSTEM_PROCESS_TERMINATED и ERROR_UNHANDLED_EXCEPTION?

Приведенный ниже код является эквивалентом 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);
}

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

  • 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";

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

#include <system_error>

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

Другие вопросы по теме