Wie soll ich FormatMessage () in C ++ richtig verwenden?

88

Ohne :

  • MFC
  • ATL

Wie kann ich FormatMessage()den Fehlertext für a HRESULTabrufen?

 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
quelle

Antworten:

133

Hier ist der richtige Weg, um eine Fehlermeldung vom System für ein HRESULT(in diesem Fall hresult genannt, oder Sie können es durch ersetzen GetLastError()) zurück zu erhalten:

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

Der Hauptunterschied zwischen dieser und der Antwort von David Hanak ist die Verwendung der FORMAT_MESSAGE_IGNORE_INSERTSFlagge. MSDN ist etwas unklar, wie Einfügungen verwendet werden sollen, aber Raymond Chen merkt an, dass Sie sie niemals verwenden sollten beim Abrufen einer Systemnachricht , da Sie nicht wissen können, welche Einfügungen das System erwartet.

FWIW, wenn Sie Visual C ++ verwenden, können Sie Ihr Leben ein bisschen einfacher machen, indem Sie die _com_errorKlasse verwenden:

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

   //automatic cleanup when error goes out of scope
}

Soweit mir bekannt, nicht direkt Teil von MFC oder ATL.

Shog9
quelle
8
Achtung: Dieser Code verwendet hResult anstelle eines Win32-Fehlercodes: Das sind verschiedene Dinge! Möglicherweise erhalten Sie den Text eines völlig anderen Fehlers als den tatsächlich aufgetretenen.
Andrei Belogortseff
1
Ausgezeichnete Punkt, @Andrei - und in der Tat, auch wenn der Fehler ist ein Win32 - Fehler, wird diese Routine nur dann erfolgreich sein , wenn es eine ist Systemfehler - ein robuster Fehlerbehandlungsmechanismus bekannt , der Quelle des Fehlers sein müssten, untersuchen Sie den Code bevor Sie FormatMessage aufrufen und stattdessen möglicherweise andere Quellen abfragen.
Shog9
1
@AndreiBelogortseff Wie kann ich wissen, was ich jeweils verwenden soll? Gibt beispielsweise a RegCreateKeyExzurück LONG. Seine docs sagt ich verwenden kann , um FormatMessageden Fehler zu holen, aber ich das werfen muß LONGin ein HRESULT.
CSL
Format () nimmt einen DWORD, @csl, eine nicht signierte ganze Zahl, wird davon ausgegangen , ein gültiger Fehlercode sein. Nicht alle Rückgabewerte - oder auch HRESULTS - sind gültige Fehlercodes. Das System geht davon aus, dass Sie dies vor dem Aufrufen der Funktion überprüft haben. In den Dokumenten für RegCreateKeyEx sollte angegeben werden, wann der Rückgabewert als Fehler interpretiert werden kann. Führen Sie diese Prüfung zuerst durch und rufen Sie erst dann FormatMessage auf.
Shog9
1
MSDN stellt ihrer Version jetzt tatsächlich den gleichen Code zur Verfügung.
ahmd0
14

Beachten Sie, dass Sie Folgendes nicht tun können:

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

   // do something with the error...

   //automatic cleanup when error goes out of scope
}

Während die Klasse auf dem Stapel erstellt und zerstört wird, bleibt errorText auf einen ungültigen Speicherort verweisen. In den meisten Fällen enthält dieser Speicherort weiterhin die Fehlerzeichenfolge, aber diese Wahrscheinlichkeit nimmt beim Schreiben von Thread-Anwendungen schnell ab.

Machen Sie es also immer wie folgt, wie von Shog9 oben beantwortet:

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

   // do something with the error...

   //automatic cleanup when error goes out of scope
}
Marius
quelle
7
Das _com_errorObjekt wird in beiden Beispielen auf dem Stapel erstellt . Der gesuchte Begriff ist vorübergehend . Im ersten Beispiel ist das Objekt ein temporäres Objekt, das am Ende der Anweisung zerstört wird.
Rob Kennedy
Ja, meinte das. Aber ich würde hoffen, dass die meisten Leute das zumindest aus dem Code herausfinden können. Technisch gesehen werden Provisorien nicht am Ende der Anweisung, sondern am Ende des Sequenzpunkts zerstört. (Das ist das gleiche in diesem Beispiel, also spaltet dies nur die Haare.)
Marius
1
Wenn Sie es sicher machen möchten (vielleicht nicht sehr effizient ), können Sie dies in C ++ tun:std::wstring strErrorText = _com_error(hresult).ErrorMessage();
ahmd0
12

Versuche dies:

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
quelle
void HandleLastError (Ergebnis)?
Aaron
1
Sicherlich können Sie diese Anpassungen selbst vornehmen.
oefe
@Atklin: Wenn Sie das Ergebnis eines Parameters verwenden möchten, benötigen Sie offensichtlich nicht die erste Zeile (GetLastError ()).
David Hanak
4
GetLastError gibt kein HResult zurück. Es wird ein Win32-Fehlercode zurückgegeben. Könnte den Namen PrintLastError bevorzugen , da dies eigentlich nicht behandeln alles. Verwenden Sie unbedingt FORMAT_MESSAGE_IGNORE_INSERTS.
Rob Kennedy
Vielen Dank für Ihre Hilfe Jungs :) - sehr geschätzt
Aaron
5

Hier ist eine Version von Davids Funktion, die Unicode verarbeitet

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
quelle
Beachten Sie, dass Sie _sntprintf_sim UNICODE-Fall nicht die richtige Puffergröße übergeben . Die Funktion nimmt die Anzahl der Zeichen an, also möchten Sie _countofoder ARRAYSIZEaka sizeof(buffer) / sizeof(buffer[0])anstelle von sizeof.
ThFabba
5

Dies ist eher eine Ergänzung zu den meisten Antworten, aber anstatt LocalFree(errorText)die HeapFreeFunktion zu verwenden:

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

Von der MSDN-Site :

Windows 10 :
LocalFree ist nicht im modernen SDK enthalten und kann daher nicht zum Freigeben des Ergebnispuffers verwendet werden. Verwenden Sie stattdessen HeapFree (GetProcessHeap (), assignMessage). In diesem Fall entspricht dies dem Aufruf von LocalFree im Speicher.

Update
Ich habe festgestellt, dass LocalFreees sich um Version 10.0.10240.0 des SDK handelt (Zeile 1108 in WinBase.h). Die Warnung ist jedoch weiterhin im obigen Link vorhanden.

#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

Update 2
Ich würde auch vorschlagen, das FORMAT_MESSAGE_MAX_WIDTH_MASKFlag zu verwenden, um Zeilenumbrüche in Systemnachrichten aufzuräumen.

Von der MSDN-Site :

FORMAT_MESSAGE_MAX_WIDTH_MASK
Die Funktion ignoriert regelmäßige Zeilenumbrüche im Nachrichtendefinitionstext. Die Funktion speichert fest codierte Zeilenumbrüche im Nachrichtendefinitionstext im Ausgabepuffer. Die Funktion generiert keine neuen Zeilenumbrüche.

Update 3
Es scheint 2 bestimmte Systemfehlercodes zu geben, die nicht die vollständige Nachricht mit dem empfohlenen Ansatz zurückgeben:

Warum erstellt FormatMessage nur Teilmeldungen für Systemfehler ERROR_SYSTEM_PROCESS_TERMINATED und ERROR_UNHANDLED_EXCEPTION?

Klassenskelett
quelle
3

Seit c ++ 11 können Sie die Standardbibliothek anstelle von FormatMessage:

#include <system_error>

std::string message = std::system_category().message(hr)
Chronial
quelle
2

Wie in anderen Antworten ausgeführt:

  • FormatMessagenimmt ein DWORDErgebnis nicht ein HRESULT(normalerweise GetLastError()).
  • LocalFree wird benötigt, um Speicher freizugeben, der von zugewiesen wurde FormatMessage

Ich nahm die obigen Punkte und fügte ein paar weitere für meine Antwort hinzu:

  • Wickeln Sie die FormatMessage in eine Klasse ein, um Speicher nach Bedarf zuzuweisen und freizugeben
  • Verwenden Sie die Operatorüberladung (z. B. operator LPTSTR() const { return ...; }damit Ihre Klasse als Zeichenfolge verwendet werden kann
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;

};

Eine vollständigere Version des obigen Codes finden Sie hier: https://github.com/stephenquan/FormatMessage

Mit der obigen Klasse ist die Verwendung einfach:

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

Der folgende Code ist Code ist das C ++ - Äquivalent, das ich im Gegensatz zu Microsoft's ErrorExit () geschrieben habe, aber leicht geändert, um alle Makros zu vermeiden und Unicode zu verwenden. Die Idee hier ist, unnötige Abgüsse und Mallocs zu vermeiden. Ich konnte nicht allen C-Casts entkommen, aber das ist das Beste, was ich aufbringen konnte. Bezieht sich auf FormatMessageW (), für das ein Zeiger von der Formatierungsfunktion und der Fehler-ID aus GetLastError () zugewiesen werden muss. Der Zeiger nach static_cast kann wie ein normaler wchar_t-Zeiger verwendet werden.

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

quelle