Como devo usar FormatMessage () corretamente em C ++?

89

Sem :

  • MFC
  • ATL

Como posso usar FormatMessage()para obter o texto de erro de um 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);
 }
Aaron
fonte

Respostas:

133

Esta é a maneira correta de obter uma mensagem de erro do sistema para um HRESULT(denominado hresult neste caso, ou você pode substituí-lo por 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;
}

A principal diferença entre isso e a resposta de David Hanak é o uso da FORMAT_MESSAGE_IGNORE_INSERTSbandeira. O MSDN não está claro como as inserções devem ser usadas, mas Raymond Chen observa que você nunca deve usá-las ao recuperar uma mensagem do sistema, pois não há como saber quais inserções o sistema espera.

FWIW, se você estiver usando Visual C ++, você pode tornar sua vida um pouco mais fácil usando a _com_errorclasse:

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

   //automatic cleanup when error goes out of scope
}

Não faz parte do MFC ou ATL diretamente, tanto quanto sei.

Shog9
fonte
8
Cuidado: este código usa hResult no lugar de um código de erro Win32: essas são coisas diferentes! Você pode obter o texto de um erro completamente diferente do que realmente ocorreu.
Andrei Belogortseff
1
Excelente ponto, @Andrei - e de fato, mesmo se o erro for um erro Win32, esta rotina só terá sucesso se for um erro do sistema - um mecanismo robusto de tratamento de erros precisaria estar ciente da origem do erro, examine o código antes de chamar FormatMessage e talvez consultar outras fontes.
Shog9,
1
@AndreiBelogortseff Como posso saber o que usar em cada caso? Por exemplo, RegCreateKeyExretorna a LONG. A documentação diz que posso usar FormatMessagepara recuperar o erro, mas preciso converter o LONGem um HRESULT.
csl
FormatMessage () recebe um DWORD, @csl, um inteiro não assinado que é considerado um código de erro válido. Nem todos os valores de retorno - ou HRESULTS para esse assunto - serão códigos de erro válidos; o sistema pressupõe que você verificou isso antes de chamar a função. Os documentos para RegCreateKeyEx devem especificar quando o valor de retorno pode ser interpretado como um erro ... Execute essa verificação primeiro e só então chame FormatMessage.
Shog9
1
Na verdade, o MSDN agora fornece sua versão do mesmo código.
ahmd0
14

Lembre-se de que você não pode fazer o seguinte:

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

   // do something with the error...

   //automatic cleanup when error goes out of scope
}

Conforme a classe é criada e destruída na pilha, deixa errorText apontar para um local inválido. Na maioria dos casos, esse local ainda conterá a string de erro, mas essa probabilidade diminui rapidamente ao gravar aplicativos encadeados.

Portanto, sempre faça da seguinte forma, conforme respondido por Shog9 acima:

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

   // do something with the error...

   //automatic cleanup when error goes out of scope
}
Marius
fonte
7
O _com_errorobjeto é criado na pilha em ambos os exemplos. O termo que você está procurando é temporário . No exemplo anterior, o objeto é um temporário que é destruído no final da instrução.
Rob Kennedy,
Sim, quis dizer isso. Mas espero que a maioria das pessoas consiga pelo menos descobrir isso a partir do código. Tecnicamente, os temporários não são destruídos no final da instrução, mas no final do ponto de sequência. (que é a mesma coisa neste exemplo, então isso é apenas uma confusão.)
Marius,
1
Se você quiser torná-lo seguro (talvez não muito eficiente ), pode fazer isso em C ++:std::wstring strErrorText = _com_error(hresult).ErrorMessage();
ahmd0
12

Experimente isto:

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);
}
David Hanak
fonte
void HandleLastError (hresult)?
Aaron
1
Certamente você mesmo pode fazer essas adaptações.
oefe
@Atklin: Se você quiser usar hresult a partir de um parâmetro, obviamente não precisa da primeira linha (GetLastError ()).
David Hanak
4
GetLastError não retorna um HResult. Ele retorna um código de erro Win32. Pode preferir o nome PrintLastError uma vez que este não é realmente lidar com qualquer coisa. E certifique-se de usar FORMAT_MESSAGE_IGNORE_INSERTS.
Rob Kennedy
Obrigado pela ajuda pessoal :) - muito apreciado
Aaron
5

Isso é mais uma adição à maioria das respostas, mas em vez de LocalFree(errorText)usar a HeapFreefunção:

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

No site do MSDN :

Windows 10 :
LocalFree não está no SDK moderno, portanto, não pode ser usado para liberar o buffer de resultado. Em vez disso, use HeapFree (GetProcessHeap (), alocadoMessage). Neste caso, é o mesmo que chamar LocalFree na memória.

A atualização
descobri que LocalFreeestá na versão 10.0.10240.0 do SDK (linha 1108 no WinBase.h). No entanto, o aviso ainda existe no link acima.

#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

Atualização 2
Eu também sugeriria usar o FORMAT_MESSAGE_MAX_WIDTH_MASKsinalizador para organizar as quebras de linha nas mensagens do sistema.

No site do MSDN :

FORMAT_MESSAGE_MAX_WIDTH_MASK
A função ignora quebras de linha regulares no texto de definição da mensagem. A função armazena quebras de linha codificadas no texto de definição da mensagem no buffer de saída. A função não gera novas quebras de linha.

Atualização 3
Parece haver 2 códigos de erro de sistema específicos que não retornam a mensagem completa usando a abordagem recomendada:

Por que FormatMessage cria apenas mensagens parciais para erros de sistema ERROR_SYSTEM_PROCESS_TERMINATED e ERROR_UNHANDLED_EXCEPTION?

Esqueleto de classe
fonte
4

Aqui está uma versão da função de David que lida com 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);

}

Oleg Zhylin
fonte
1
Observe que você não está passando o tamanho correto do buffer _sntprintf_sno caso UNICODE. A função leva o número de caracteres, então você quer _countofou ARRAYSIZEaka em sizeof(buffer) / sizeof(buffer[0])vez de sizeof.
ThFabba
3

Desde c ++ 11, você pode usar a biblioteca padrão em vez de FormatMessage:

#include <system_error>

std::string message = std::system_category().message(hr)
Crônico
fonte
2

Conforme apontado em outras respostas:

  • FormatMessageleva um DWORDresultado, não um HRESULT(normalmente GetLastError()).
  • LocalFree é necessário para liberar memória que foi alocada por FormatMessage

Eu peguei os pontos acima e acrescentei mais alguns para minha resposta:

  • Envolva o FormatMessageem uma classe para alocar e liberar memória conforme necessário
  • Use a sobrecarga do operador (por exemplo, operator LPTSTR() const { return ...; }para que sua classe possa ser usada como uma string
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;

};

Encontre uma versão mais completa do código acima aqui: https://github.com/stephenquan/FormatMessage

Com a classe acima, o uso é simplesmente:

    std::wcout << (LPTSTR) CFormatMessage(GetLastError()) << L"\n";
Stephen Quan
fonte
0

O código a seguir é o código C ++ equivalente que escrevi em contraste com o ErrorExit () da Microsoft, mas ligeiramente alterado para evitar todas as macros e usar Unicode. A ideia aqui é evitar moldes e malhos desnecessários. Não consegui escapar de todos os elencos C, mas foi o melhor que consegui reunir. Pertencente a FormatMessageW (), que requer um ponteiro a ser alocado pela função de formato e o Id de erro de GetLastError (). O ponteiro após static_cast pode ser usado como um ponteiro wchar_t normal.

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

fonte