Come devo utilizzare correttamente FormatMessage () in C ++?


90

Senza :

  • MFC
  • ATL

Come posso utilizzare FormatMessage()per ottenere il testo dell'errore per un 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);
 }

Risposte:


134

Ecco il modo corretto per ricevere un messaggio di errore dal sistema per un HRESULT(denominato hresult in questo caso, oppure puoi sostituirlo con 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;
}

La differenza fondamentale tra questo e la risposta di David Hanak è l'uso della FORMAT_MESSAGE_IGNORE_INSERTSbandiera. MSDN non è un po 'chiaro su come dovrebbero essere usati gli inserimenti, ma Raymond Chen osserva che non dovresti mai usarli quando recuperi un messaggio di sistema, poiché non hai modo di sapere quali inserimenti il ​​sistema si aspetta.

FWIW, se stai usando Visual C ++ puoi semplificarti la vita usando la _com_errorclasse:

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

   //automatic cleanup when error goes out of scope
}

Non fa parte di MFC o ATL direttamente per quanto ne so.


8
Attenzione: questo codice utilizza hResult al posto di un codice di errore Win32: quelle sono cose diverse! È possibile che venga visualizzato il testo di un errore completamente diverso da quello che si è effettivamente verificato.
Andrei Belogortseff

1
Punto eccellente, @Andrei - e in effetti, anche se l'errore è un errore Win32, questa routine avrà successo solo se si tratta di un errore di sistema - un robusto meccanismo di gestione degli errori dovrebbe essere a conoscenza della fonte dell'errore, esamina il codice prima di chiamare FormatMessage e magari interrogare altre fonti.
Shog9

1
@AndreiBelogortseff Come faccio a sapere cosa usare in ogni caso? Ad esempio, RegCreateKeyExrestituisce un file LONG. I suoi documenti dicono che posso usare FormatMessageper recuperare l'errore, ma devo lanciare il file LONGin un file HRESULT.
csl

FormatMessage () accetta un DWORD, @csl, un numero intero senza segno che si presume sia un codice di errore valido. Non tutti i valori restituiti, o HRESULT per quella materia, saranno codici di errore validi; il sistema presume che tu abbia verificato che sia prima di chiamare la funzione. La documentazione per RegCreateKeyEx deve specificare quando il valore restituito può essere interpretato come un errore ... Eseguire prima il controllo e solo successivamente chiamare FormatMessage.
Shog9

1
MSDN in realtà ora fornisce la loro versione di un po 'lo stesso codice.
ahmd0

14

Tieni presente che non puoi eseguire le seguenti operazioni:

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

   // do something with the error...

   //automatic cleanup when error goes out of scope
}

Quando la classe viene creata e distrutta nello stack, errorText punta a una posizione non valida. Nella maggior parte dei casi questa posizione conterrà ancora la stringa di errore, ma tale probabilità svanisce rapidamente durante la scrittura di applicazioni in thread.

Quindi fallo sempre come segue come risposto da Shog9 sopra:

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

   // do something with the error...

   //automatic cleanup when error goes out of scope
}

7
L' _com_erroroggetto viene creato in pila in entrambi gli esempi. Il termine che stai cercando è temporaneo . Nel primo esempio, l'oggetto è un temporaneo che viene distrutto alla fine dell'istruzione.
Rob Kennedy,

Sì, significava quello. Ma spero che la maggior parte delle persone sia almeno in grado di capirlo dal codice. Tecnicamente i provvisori non vengono distrutti alla fine della dichiarazione, ma alla fine del punto sequenza. (che è la stessa cosa in questo esempio, quindi questo è solo spaccare i capelli.)
Marius

1
Se vuoi renderlo sicuro (forse non molto efficiente ) puoi farlo in C ++:std::wstring strErrorText = _com_error(hresult).ErrorMessage();
ahmd0

11

Prova questo:

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

1
Sicuramente puoi fare questi adattamenti da solo.
oefe

@ Atklin: se vuoi usare hresult da un parametro, ovviamente non hai bisogno della prima riga (GetLastError ()).
David Hanak,

4
GetLastError non restituisce un HResult. Restituisce un codice di errore Win32. Potrebbe preferire il nome PrintLastError poiché questo in realtà non gestisce nulla. E assicurati di utilizzare FORMAT_MESSAGE_IGNORE_INSERTS.
Rob Kennedy,

Grazie per il vostro aiuto ragazzi :) - molto apprezzato
Aaron

5

Questa è più un'aggiunta alla maggior parte delle risposte, ma invece di utilizzare LocalFree(errorText)la HeapFreefunzione:

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

Dal sito MSDN :

Windows 10 :
LocalFree non è nel moderno SDK, quindi non può essere utilizzato per liberare il buffer dei risultati. Utilizzare invece HeapFree (GetProcessHeap (), allocatoMessage). In questo caso, è come chiamare LocalFree in memoria.

Aggiornamento
ho scoperto che LocalFreeè nella versione 10.0.10240.0 dell'SDK (riga 1108 in WinBase.h). Tuttavia, l'avviso è ancora presente nel collegamento precedente.

#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

Aggiornamento 2
Suggerirei anche di utilizzare il FORMAT_MESSAGE_MAX_WIDTH_MASKflag per riordinare le interruzioni di riga nei messaggi di sistema.

Dal sito MSDN :

FORMAT_MESSAGE_MAX_WIDTH_MASK
La funzione ignora le interruzioni di riga regolari nel testo di definizione del messaggio. La funzione memorizza interruzioni di riga hardcoded nel testo di definizione del messaggio nel buffer di output. La funzione non genera nuove interruzioni di riga.

Aggiornamento 3
Sembra che ci siano 2 codici di errore di sistema particolari che non restituiscono il messaggio completo utilizzando l'approccio consigliato:

Perché FormatMessage crea solo messaggi parziali per errori di sistema ERROR_SYSTEM_PROCESS_TERMINATED ed ERROR_UNHANDLED_EXCEPTION?


4

Ecco una versione della funzione di David che gestisce 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
Nota che non stai passando la dimensione del buffer corretta _sntprintf_snel caso UNICODE. La funzione prende il numero di caratteri, quindi vuoi _countofo ARRAYSIZEaka sizeof(buffer) / sizeof(buffer[0])invece di sizeof.
ThFabba

4

A partire da c ++ 11, puoi usare la libreria standard invece di FormatMessage:

#include <system_error>

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

2

Come sottolineato in altre risposte:

  • FormatMessageprende un DWORDrisultato non un HRESULT(tipicamente GetLastError()).
  • LocalFree è necessario per rilasciare la memoria allocata da FormatMessage

Ho preso i punti precedenti e ne ho aggiunti altri per la mia risposta:

  • Avvolgi il FormatMessagein una classe per allocare e rilasciare la memoria secondo necessità
  • Usa l'overload dell'operatore (ad es. In operator LPTSTR() const { return ...; }modo che la tua classe possa essere usata come stringa
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;

};

Trova una versione più completa del codice sopra qui: https://github.com/stephenquan/FormatMessage

Con la classe precedente, l'utilizzo è semplicemente:

    std::wcout << (LPTSTR) CFormatMessage(GetLastError()) << L"\n";

0

Il codice seguente è il codice è l'equivalente C ++ che ho scritto in contrasto con ErrorExit () di Microsoft ma leggermente modificato per evitare tutte le macro e utilizzare Unicode. L'idea qui è di evitare cast e mallocs non necessari. Non sono riuscito a sfuggire a tutti i cast di C, ma questo è il meglio che ho potuto raccogliere. Relativo a FormatMessageW (), che richiede l'allocazione di un puntatore dalla funzione di formattazione e l'ID errore da GetLastError (). Il puntatore dopo static_cast può essere utilizzato come un normale puntatore 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);
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.