Wie erhalte ich die Fehlermeldung aus dem von GetLastError () zurückgegebenen Fehlercode?


137

Wie kann ich nach einem Windows-API-Aufruf die letzte Fehlermeldung in Textform erhalten?

GetLastError() Gibt einen ganzzahligen Wert zurück, keine Textnachricht.


Früher gab es im Tool-Bereich von Visual Studio eine exe-Fehlersuche, die dies ziemlich gut macht, wenn Sie nur eine Fehlermeldung zum Debuggen benötigen.
ColdCat

@ColdCat: Zum Debuggen ist es viel einfacher, einfach eine @err,hrUhr hinzuzufügen und den Debugger den letzten Fehlercode automatisch in eine für Menschen lesbare Darstellung konvertieren zu lassen. Der ,hrFormatbezeichner funktioniert für jeden Ausdruck, der einen ganzzahligen Wert ergibt, z. B. zeigt eine 5,hrUhr "ERROR_ACCESS_DENIED: Zugriff verweigert" an. .
Unsichtbarer

2
Aus der GetLastError()Dokumentation: " Verwenden Sie die FormatMessage()Funktion, um eine Fehlerzeichenfolge für Systemfehlercodes zu erhalten . " Siehe das Beispiel zum Abrufen des Codes für den letzten Fehler in MSDN.
Remy Lebeau

Antworten:


145
//Returns the last Win32 error, in string format. Returns an empty string if there is no error.
std::string GetLastErrorAsString()
{
    //Get the error message, if any.
    DWORD errorMessageID = ::GetLastError();
    if(errorMessageID == 0)
        return std::string(); //No error message has been recorded

    LPSTR messageBuffer = nullptr;
    size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                                 NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL);

    std::string message(messageBuffer, size);

    //Free the buffer.
    LocalFree(messageBuffer);

    return message;
}

2
Ich glaube, Sie müssen (LPSTR)&messageBufferin diesem Fall tatsächlich übergeben , da FormatMessageA sonst seinen Wert nicht so ändern kann, dass er auf den zugewiesenen Puffer verweist.
Kylotan

2
Oh, wow, ja das ist irgendwie komisch. Wie würde es den Zeiger ändern? Aber die Adresse des Zeigers (Zeiger auf einen Zeiger) übergeben, aber in einen regulären Zeiger umwandeln ... Win32-Verrücktheit. Vielen Dank für das Heads-up, das in meiner eigenen Codebasis (und meiner Antwort) behoben wurde. Sehr subtiler Fang.
Jamin Gray

1
Vielen Dank, Ihr Beispiel ist viel klarer als das von MSDN. Darüber hinaus konnte der von MSDN nicht einmal kompiliert werden. Es enthält einige strsafe.hHeader, die überhaupt nicht sicher sind und eine Reihe von Compilerfehlern in winuser.hund verursachen winbase.h.
Hi-Angel

2
Einige Fehler-IDs werden nicht unterstützt. Beispiel: 0x2EE7, ERROR_INTERNET_NAME_NOT_RESOLVED verursacht beim Aufrufen von FormatMessage einen neuen Fehler: 0x13D. Das System kann den Nachrichtentext für die Nachrichtennummer 0x% 1 in der Nachrichtendatei für% 2 nicht finden.
Brent

3
Probleme mit dieser Implementierung: 1 GetLastErrorwird möglicherweise zu spät aufgerufen. 2Keine Unicode-Unterstützung. 3Verwendung von Ausnahmen ohne Implementierung von Ausnahmesicherheitsgarantien.
Unsichtbarer

65

Aktualisiert (11/2017), um einige Kommentare zu berücksichtigen.

Einfaches Beispiel:

wchar_t buf[256];
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
               NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 
               buf, (sizeof(buf) / sizeof(wchar_t)), NULL);

3
@ Hi-Angel - Das Beispiel setzt voraus, dass Sie mit UNICODE definiert kompilieren. 'FormatMessage' ist eigentlich ein Makro, das je nach Kompilierung der Anwendung entweder zu 'FormatMessageA' für Ansi / MBCS-Zeichenpuffer oder zu 'FormatMessageW' für UTF16 / UNICODE-Puffer erweitert wird. Ich habe mir erlaubt, das obige Beispiel zu bearbeiten, um explizit die Version aufzurufen, die dem Ausgabepuffertyp (wchar_t) entspricht.
Bukes

2
Fügen Sie FORMAT_MESSAGE_IGNORE_INSERTS hinzu: "Wenn Sie nicht die Kontrolle über die Formatzeichenfolge haben, müssen Sie FORMAT_MESSAGE_IGNORE_INSERTS übergeben, um zu verhindern, dass% 1 Probleme verursacht." blogs.msdn.microsoft.com/oldnewthing/20071128-00/?p=24353
Roi Danton

1
Probleme mit dieser Implementierung: 1Fehler beim Angeben des wichtigen FORMAT_MESSAGE_IGNORE_INSERTSFlags. 2 GetLastErrormöglicherweise zu spät angerufen. 3Beliebige Beschränkung der Nachricht auf 256 Codeeinheiten. 4Keine Fehlerbehandlung.
Unsichtbarer

1
sizeof (buf) sollte ARRAYSIZE (buf) sein, da FormatMessage die Größe des Puffers in TCHARs und nicht in Bytes erwartet.
Kai

1
Können wir nicht 256anstelle von verwenden (sizeof(buf) / sizeof(wchar_t)? ist es akzeptabel und sicher?
BattleTested



14

GetLastError gibt einen numerischen Fehlercode zurück. Um eine beschreibende Fehlermeldung zu erhalten (z. B. um sie einem Benutzer anzuzeigen), können Sie FormatMessage aufrufen :

// This functions fills a caller-defined character buffer (pBuffer)
// of max length (cchBufferLength) with the human-readable error message
// for a Win32 error code (dwErrorCode).
// 
// Returns TRUE if successful, or FALSE otherwise.
// If successful, pBuffer is guaranteed to be NUL-terminated.
// On failure, the contents of pBuffer are undefined.
BOOL GetErrorMessage(DWORD dwErrorCode, LPTSTR pBuffer, DWORD cchBufferLength)
{
    if (cchBufferLength == 0)
    {
        return FALSE;
    }

    DWORD cchMsg = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                                 NULL,  /* (not used with FORMAT_MESSAGE_FROM_SYSTEM) */
                                 dwErrorCode,
                                 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                                 pBuffer,
                                 cchBufferLength,
                                 NULL);
    return (cchMsg > 0);
}

In C ++ können Sie die Schnittstelle mithilfe der Klasse std :: string erheblich vereinfachen:

#include <Windows.h>
#include <system_error>
#include <memory>
#include <string>
typedef std::basic_string<TCHAR> String;

String GetErrorMessage(DWORD dwErrorCode)
{
    LPTSTR psz{ nullptr };
    const DWORD cchMsg = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM
                                         | FORMAT_MESSAGE_IGNORE_INSERTS
                                         | FORMAT_MESSAGE_ALLOCATE_BUFFER,
                                       NULL, // (not used with FORMAT_MESSAGE_FROM_SYSTEM)
                                       dwErrorCode,
                                       MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                                       reinterpret_cast<LPTSTR>(&psz),
                                       0,
                                       NULL);
    if (cchMsg > 0)
    {
        // Assign buffer to smart pointer with custom deleter so that memory gets released
        // in case String's c'tor throws an exception.
        auto deleter = [](void* p) { ::LocalFree(p); };
        std::unique_ptr<TCHAR, decltype(deleter)> ptrBuffer(psz, deleter);
        return String(ptrBuffer.get(), cchMsg);
    }
    else
    {
        auto error_code{ ::GetLastError() };
        throw std::system_error( error_code, std::system_category(),
                                 "Failed to retrieve error message string.");
    }
}

HINWEIS: Diese Funktionen funktionieren auch für HRESULT-Werte. Ändern Sie einfach den ersten Parameter von DWORD dwErrorCode in HRESULT hResult. Der Rest des Codes kann unverändert bleiben.


Diese Implementierungen bieten die folgenden Verbesserungen gegenüber den vorhandenen Antworten:

  • Vollständiger Beispielcode, nicht nur ein Verweis auf die aufzurufende API.
  • Bietet sowohl C- als auch C ++ - Implementierungen.
  • Funktioniert sowohl für Unicode- als auch für MBCS-Projekteinstellungen.
  • Nimmt den Fehlercode als Eingabeparameter. Dies ist wichtig, da der letzte Fehlercode eines Threads nur an genau definierten Punkten gültig ist. Ein Eingabeparameter ermöglicht es dem Anrufer, dem dokumentierten Vertrag zu folgen.
  • Implementiert die richtige Ausnahmesicherheit. Im Gegensatz zu allen anderen Lösungen, die implizit Ausnahmen verwenden, geht bei dieser Implementierung kein Speicher verloren, falls beim Erstellen des Rückgabewerts eine Ausnahme ausgelöst wird.
  • Ordnungsgemäße Verwendung der FORMAT_MESSAGE_IGNORE_INSERTSFlagge. Weitere Informationen finden Sie unter Die Bedeutung des FORMAT_MESSAGE_IGNORE_INSERTS-Flags .
  • Richtige Fehlerbehandlung / Fehlerberichterstattung, im Gegensatz zu einigen anderen Antworten, bei denen Fehler stillschweigend ignoriert werden.


Diese Antwort wurde aus der Stapelüberlaufdokumentation übernommen. Die folgenden Benutzer haben zu dem Beispiel beigetragen: stackptr , Ajay , Cody Gray ♦ , IInspectable .


1
Anstatt zu werfen std::runtime_error, schlage ich vor, zu werfen, std::system_error(lastError, std::system_category(), "Failed to retrieve error message string.")wo lastErrorder Rückgabewert GetLastError()nach dem fehlgeschlagenen FormatMessage()Aufruf wäre.
Zett42

Was ist der Vorteil einer FormatMessagedirekten Verwendung Ihrer C-Version ?
Micha Wiedenmann

@mic: Das ist wie zu fragen: "Was ist der Vorteil von Funktionen in C?" Funktionen reduzieren die Komplexität durch Bereitstellung von Abstraktionen. In diesem Fall reduziert die Abstraktion die Anzahl der Parameter von 7 auf 3, implementiert den dokumentierten Vertrag der API durch Übergeben kompatibler Flags und Parameter und codiert die dokumentierte Fehlerberichterstattungslogik. Es bietet eine übersichtliche Oberfläche mit leicht zu erratender Semantik, sodass Sie nicht 11 Minuten investieren müssen, um die Dokumentation zu lesen .
Unsichtbarer

Ich mag diese Antwort, es ist sehr klar, dass der Richtigkeit des Codes besondere Aufmerksamkeit geschenkt wurde. Ich habe zwei Fragen. 1) Warum verwenden Sie ::HeapFree(::GetProcessHeap(), 0, p)den Deleter anstelle ::LocalFree(p)der Dokumentation? 2) Mir ist klar, dass die Dokumentation dies so vorschreibt, aber nicht reinterpret_cast<LPTSTR>(&psz)gegen die strenge Aliasing-Regel verstößt?
JoE

@teh: Danke für das Feedback. Frage 1): Ehrlich gesagt weiß ich nicht, ob dies sicher ist. Ich erinnere mich nicht, wer oder warum der Aufruf an HeapFreeeingeführt wurde, während dies noch ein Thema in der SO-Dokumentation war. Ich muss nachforschen (obwohl die Dokumentation klar zu sein scheint, dass dies nicht sicher ist). Frage 2): Dies ist zweifach. Ist für eine MBCS-Konfiguration LPTSTRein Alias ​​für char*. Diese Besetzung ist immer sicher. Bei einer Unicode-Konfiguration ist das Casting wchar_t*auf jeden Fall faul. Ich weiß nicht, ob dies in C sicher ist, aber es ist höchstwahrscheinlich nicht in C ++. Ich würde das auch untersuchen müssen.
Unsichtbarer

13

Im Allgemeinen müssen Sie verwenden FormatMessage , um von einem Win32-Fehlercode in Text zu konvertieren.

Aus der MSDN- Dokumentation :

Formatiert eine Nachrichtenzeichenfolge. Die Funktion erfordert eine Nachrichtendefinition als Eingabe. Die Nachrichtendefinition kann aus einem Puffer stammen, der an die Funktion übergeben wird. Es kann aus einer Nachrichtentabellenressource in einem bereits geladenen Modul stammen. Oder der Anrufer kann die Funktion auffordern, die Nachrichtentabellenressourcen des Systems nach der Nachrichtendefinition zu durchsuchen. Die Funktion findet die Nachrichtendefinition in einer Nachrichtentabellenressource basierend auf einer Nachrichtenkennung und einer Sprachkennung. Die Funktion kopiert den formatierten Nachrichtentext in einen Ausgabepuffer und verarbeitet auf Anfrage alle eingebetteten Einfügesequenzen.

Die Deklaration von FormatMessage:

DWORD WINAPI FormatMessage(
  __in      DWORD dwFlags,
  __in_opt  LPCVOID lpSource,
  __in      DWORD dwMessageId, // your error code
  __in      DWORD dwLanguageId,
  __out     LPTSTR lpBuffer,
  __in      DWORD nSize,
  __in_opt  va_list *Arguments
);

7

Wenn Sie c # verwenden, können Sie diesen Code verwenden:

using System.Runtime.InteropServices;

public static class WinErrors
{
    #region definitions
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern IntPtr LocalFree(IntPtr hMem);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern int FormatMessage(FormatMessageFlags dwFlags, IntPtr lpSource, uint dwMessageId, uint dwLanguageId, ref IntPtr lpBuffer, uint nSize, IntPtr Arguments);

    [Flags]
    private enum FormatMessageFlags : uint
    {
        FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100,
        FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200,
        FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000,
        FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000,
        FORMAT_MESSAGE_FROM_HMODULE = 0x00000800,
        FORMAT_MESSAGE_FROM_STRING = 0x00000400,
    }
    #endregion

    /// <summary>
    /// Gets a user friendly string message for a system error code
    /// </summary>
    /// <param name="errorCode">System error code</param>
    /// <returns>Error string</returns>
    public static string GetSystemMessage(int errorCode)
    {
        try
        {
            IntPtr lpMsgBuf = IntPtr.Zero;

            int dwChars = FormatMessage(
                FormatMessageFlags.FORMAT_MESSAGE_ALLOCATE_BUFFER | FormatMessageFlags.FORMAT_MESSAGE_FROM_SYSTEM | FormatMessageFlags.FORMAT_MESSAGE_IGNORE_INSERTS,
                IntPtr.Zero,
                (uint) errorCode,
                0, // Default language
                ref lpMsgBuf,
                0,
                IntPtr.Zero);
            if (dwChars == 0)
            {
                // Handle the error.
                int le = Marshal.GetLastWin32Error();
                return "Unable to get error code string from System - Error " + le.ToString();
            }

            string sRet = Marshal.PtrToStringAnsi(lpMsgBuf);

            // Free the buffer.
            lpMsgBuf = LocalFree(lpMsgBuf);
            return sRet;
        }
        catch (Exception e)
        {
            return "Unable to get error code string from System -> " + e.ToString();
        }
    }
}

Ich habe bestätigt, dass dieser Code funktioniert. Sollte TS diese Antwort nicht akzeptieren?
Swdev

2
Wenn es für ein weiteres Werfen notwendig ist, gibt es eine einfachere Möglichkeit , dies in C # mit Win32Exception
SerG

2
@swdev: Warum sollte jemand eine Antwort in C # auf eine Frage mit dem Tag c oder c ++ akzeptieren ? Diese Antwort geht derzeit nicht einmal auf die gestellte Frage ein.
Unsichtbarer

1
Ich kann mich nicht erinnern, über diese vorgeschlagene Antwort abgestimmt zu haben. Ich habe den offensichtlichen Fehler in der Logik von @ swdev angesprochen. Aber da Sie mir nicht glauben werden, werde ich es Ihnen jetzt beweisen: Hier, haben Sie eine weitere Ablehnung. Diese ist von mir, weil diese Antwort - obwohl sie bei einer anderen Frage nützlich sein kann - angesichts der gestellten Frage einfach nicht nützlich ist.
Unsichtbarer

2
"Ich denke, Sie haben hilfreiche Einblicke, die über das Offensichtliche hinausgehen" - in der Tat .
Unsichtbarer

4

Wenn Sie sowohl MBCS als auch Unicode unterstützen müssen, reicht die Antwort von Mr.C64 nicht aus. Der Puffer muss als TCHAR deklariert und in LPTSTR umgewandelt werden. Beachten Sie, dass dieser Code nicht die nervige neue Zeile behandelt, die Microsoft an die Fehlermeldung anfügt.

CString FormatErrorMessage(DWORD ErrorCode)
{
    TCHAR   *pMsgBuf = NULL;
    DWORD   nMsgLen = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL, ErrorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        reinterpret_cast<LPTSTR>(&pMsgBuf), 0, NULL);
    if (!nMsgLen)
        return _T("FormatMessage fail");
    CString sMsg(pMsgBuf, nMsgLen);
    LocalFree(pMsgBuf);
    return sMsg;
}

Der Kürze halber finde ich auch die folgende Methode nützlich:

CString GetLastErrorString()
{
    return FormatErrorMessage(GetLastError());
}

Falls der CStringc'tor eine Ausnahme auslöst, verliert diese Implementierung den durch den Aufruf an zugewiesenen Speicher FormatMessage.
Unsichtbarer

Stimmt, aber ich habe diesen Code seit vielen Jahren verwendet und es war nie ein Problem. Der einzige Fall, in dem der CString-Ctor wahrscheinlich einen Fehler verursacht, ist ein Fehler beim Zuweisen von Speicher, und der meiste MFC-Code - einschließlich des von Microsoft bereitgestellten Materials - verarbeitet nicht genügend Speicherbedingungen, wie Sie möchten. Glücklicherweise haben die meisten PCs jetzt so viel Speicher, dass Sie ziemlich hart arbeiten müssen, um alles zu nutzen. Bei jeder Verwendung, die eine temporäre CString-Instanz generiert (einschließlich der Rückgabe eines CString), besteht dieses Risiko. Es ist ein Kompromiss zwischen Risiko und Zweckmäßigkeit. Auch wenn es in einem Nachrichtenhandler passiert, wird die Ausnahme abgefangen.
Opfer der Freizeit

Der größte Teil dieses Kommentars ist falsch, sorry. "Mir ist es nie passiert" ist eine verdammte Schwachstelle, besonders wenn Sie wissen, wie der Code fehlschlagen kann. Die Speichermenge hat auch keinen Einfluss auf den verfügbaren Adressraum, der einem Prozess zugewiesen ist. RAM ist nur eine Leistungsoptimierung. Copy-Elision verhindert die Zuweisung eines temporären Elements, wenn Code geschrieben wird, um NRVO zu ermöglichen. Ob Ausnahmen in einem Nachrichtenhandler abgefangen werden oder nicht, hängt von der Prozessbitness und den externen Einstellungen ab. Ich habe eine Antwort eingereicht, die zeigt, dass Risikomanagement nicht zu Unannehmlichkeiten führt.
Unsichtbarer

Außerdem besteht das Risiko, dass beim Erstellen eines temporären Objekts nicht genügend Speicherplatz zur Verfügung steht. Zu diesem Zeitpunkt wurden alle Ressourcen freigegeben, und es wird nichts Schlimmes daraus.
Unsichtbarer

1
Nein, es tut mir leid, dass der Code für Sie nicht nützlich ist. Aber TBH ist ziemlich niedrig auf meiner Liste der Probleme.
Opfer der Freizeit

3
void WinErrorCodeToString(DWORD ErrorCode, string& Message)
{
char* locbuffer = NULL;
DWORD count = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL, ErrorCode,
    0, (LPSTR)&locbuffer, 0, nullptr);
if (locbuffer)
{
    if (count)
    {
        int c;
        int back = 0;
        //
        // strip any trailing "\r\n"s and replace by a single "\n"
        //
        while (((c = *CharPrevA(locbuffer, locbuffer + count)) == '\r') ||
            (c == '\n')) {
            count--;
            back++;
        }

        if (back) {
            locbuffer[count++] = '\n';
            locbuffer[count] = '\0';
        }

        Message = "Error: ";
        Message += locbuffer;
    }
    LocalFree(locbuffer);
}
else
{
    Message = "Unknown error code: " + to_string(ErrorCode);
}
}

Könnten Sie auch eine Erklärung hinzufügen?
Robert

1
Probleme mit dieser Implementierung: 1Keine Unicode-Unterstützung. 2Unangemessene Formatierung der Fehlermeldung. Wenn der Aufrufer die zurückgegebene Zeichenfolge verarbeiten muss, kann er dies einfach tun. Ihre Implementierung lässt dem Anrufer keine Option. 3Verwendung von Ausnahmen, aber mangelnde angemessene Ausnahmesicherheit. Falls die std::stringOperatoren Ausnahmen auslösen, ist der von zugewiesene Puffer FormatMessagedurchgesickert. 4Warum nicht einfach ein zurückgeben, std::stringanstatt den Anrufer ein Objekt als Referenz übergeben zu lassen?
Unsichtbarer

1

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

#include <system_error>

std::string GetLastErrorAsString(){
    DWORD errorMessageID = ::GetLastError();
    if (errorMessageID == 0) {
        return std::string(); //No error message has been recorded
    } else {
        return std::system_category().message(errorMessageID);
    }
}

Es gibt nur ein winziges Fenster, in dem das Aufrufen GetLastErrorein aussagekräftiges Ergebnis liefert. Da dies C ++ ist, besteht die einzig sichere Option darin, dass der Anrufer den letzten Fehlercode bereitstellt. Es hilft sicherlich nicht, dass der präsentierte Code GetLastError zweimal aufruft . Auch wenn dies praktisch ist, macht das Fehlen einer breiten Zeichenunterstützung in C ++ die error_categoryBenutzeroberfläche nicht universell nützlich. Dies trägt nur zu C ++ 'langer Geschichte verpasster Gelegenheiten bei.
Unsichtbar

Guter Punkt über den nutzlosen Anruf bei GetLastError. Aber ich sehe keinen Unterschied zwischen einem Anruf GetLastErrorhier oder einem Anruf des Anrufers. In Bezug auf C ++ und wchar: Geben Sie die Hoffnung nicht auf, Microsoft beginnt damit, Apps nur UTF-8 zuzulassen .
Chronial

"Ich sehe keinen Unterschied" - Betrachten Sie die folgende Anrufseite : log_error("error", GetLastErrorAsString());. Bedenken Sie auch, dass log_errordas erste Argument vom Typ ist std::string. Der (unsichtbare) Conversion-Aufruf hat gerade Ihre Garantien gelöscht, um einen aussagekräftigen Wert GetLastErroran dem Punkt zu erfassen, an dem Sie ihn aufrufen.
Unsichtbar

Warum ruft der Konstruktor std :: string Ihrer Meinung nach eine Win32-Funktion auf?
Chronial

1
mallocruft HeapAllocnach dem Prozessheap. Der Prozesshaufen ist erweiterbar. Wenn es wachsen muss, wird es nennen letztlich VirtualAlloc , die sich der anrufenden Thread des letzten Fehlercode gesetzt. Nun, das ist völlig verfehlt: C ++ ist ein Minenfeld. Diese Implementierung trägt nur dazu bei, indem sie eine Schnittstelle mit impliziten Garantien bereitstellt, denen sie einfach nicht gerecht werden kann. Wenn Sie der Meinung sind, dass es kein Problem gibt, sollten Sie die Richtigkeit der vorgeschlagenen Lösung leicht nachweisen können . Viel Glück.
Unsichtbar

0

Ich werde dies hier lassen, da ich es später verwenden muss. Es ist eine Quelle für ein kleines binär kompatibles Tool, das in Assembly, C und C ++ gleich gut funktioniert.

GetErrorMessageLib.c (kompiliert zu GetErrorMessageLib.dll)

#include <Windows.h>

/***
 * returns 0 if there was enough space, size of buffer in bytes needed
 * to fit the result, if there wasn't enough space. -1 on error.
 */
__declspec(dllexport)
int GetErrorMessageA(DWORD dwErrorCode, LPSTR lpResult, DWORD dwBytes)
{    
    LPSTR tmp;
    DWORD result_len;

    result_len = FormatMessageA (
        FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER,
        NULL,
        dwErrorCode,
        LANG_SYSTEM_DEFAULT,
        (LPSTR)&tmp,
        0,
        NULL
    );        

    if (result_len == 0) {
        return -1;
    }

    // FormatMessage's return is 1 character too short.
    ++result_len;

    strncpy(lpResult, tmp, dwBytes);

    lpResult[dwBytes - 1] = 0;
    LocalFree((HLOCAL)tmp);

    if (result_len <= dwBytes) {
        return 0;
    } else {
        return result_len;
    }
}

/***
 * returns 0 if there was enough space, size of buffer in bytes needed
 * to fit the result, if there wasn't enough space. -1 on error.
 */
__declspec(dllexport)
int GetErrorMessageW(DWORD dwErrorCode, LPWSTR lpResult, DWORD dwBytes)
{   
    LPWSTR tmp;
    DWORD nchars;
    DWORD result_bytes;

    nchars = dwBytes >> 1;

    result_bytes = 2 * FormatMessageW (
        FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER,
        NULL,
        dwErrorCode,
        LANG_SYSTEM_DEFAULT,
        (LPWSTR)&tmp,
        0,
        NULL
    );    

    if (result_bytes == 0) {
        return -1;
    } 

    // FormatMessage's return is 1 character too short.
    result_bytes += 2;

    wcsncpy(lpResult, tmp, nchars);
    lpResult[nchars - 1] = 0;
    LocalFree((HLOCAL)tmp);

    if (result_bytes <= dwBytes) {
        return 0;
    } else {
        return result_bytes * 2;
    }
}

Inline-Version (GetErrorMessage.h):

#ifndef GetErrorMessage_H 
#define GetErrorMessage_H 
#include <Windows.h>    

/***
 * returns 0 if there was enough space, size of buffer in bytes needed
 * to fit the result, if there wasn't enough space. -1 on error.
 */
static inline int GetErrorMessageA(DWORD dwErrorCode, LPSTR lpResult, DWORD dwBytes)
{    
    LPSTR tmp;
    DWORD result_len;

    result_len = FormatMessageA (
        FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER,
        NULL,
        dwErrorCode,
        LANG_SYSTEM_DEFAULT,
        (LPSTR)&tmp,
        0,
        NULL
    );        

    if (result_len == 0) {
        return -1;
    }

    // FormatMessage's return is 1 character too short.
    ++result_len;

    strncpy(lpResult, tmp, dwBytes);

    lpResult[dwBytes - 1] = 0;
    LocalFree((HLOCAL)tmp);

    if (result_len <= dwBytes) {
        return 0;
    } else {
        return result_len;
    }
}

/***
 * returns 0 if there was enough space, size of buffer in bytes needed
 * to fit the result, if there wasn't enough space. -1 on error.
 */
static inline int GetErrorMessageW(DWORD dwErrorCode, LPWSTR lpResult, DWORD dwBytes)
{   
    LPWSTR tmp;
    DWORD nchars;
    DWORD result_bytes;

    nchars = dwBytes >> 1;

    result_bytes = 2 * FormatMessageW (
        FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER,
        NULL,
        dwErrorCode,
        LANG_SYSTEM_DEFAULT,
        (LPWSTR)&tmp,
        0,
        NULL
    );    

    if (result_bytes == 0) {
        return -1;
    } 

    // FormatMessage's return is 1 character too short.
    result_bytes += 2;

    wcsncpy(lpResult, tmp, nchars);
    lpResult[nchars - 1] = 0;
    LocalFree((HLOCAL)tmp);

    if (result_bytes <= dwBytes) {
        return 0;
    } else {
        return result_bytes * 2;
    }
}

#endif /* GetErrorMessage_H */

dynamischer Anwendungsfall (vorausgesetzt, der Fehlercode ist gültig, andernfalls ist eine -1-Prüfung erforderlich):

#include <Windows.h>
#include <Winbase.h>
#include <assert.h>
#include <stdio.h>

int main(int argc, char **argv)
{   
    int (*GetErrorMessageA)(DWORD, LPSTR, DWORD);
    int (*GetErrorMessageW)(DWORD, LPWSTR, DWORD);
    char result1[260];
    wchar_t result2[260];

    assert(LoadLibraryA("GetErrorMessageLib.dll"));

    GetErrorMessageA = (int (*)(DWORD, LPSTR, DWORD))GetProcAddress (
        GetModuleHandle("GetErrorMessageLib.dll"),
        "GetErrorMessageA"
    );        
    GetErrorMessageW = (int (*)(DWORD, LPWSTR, DWORD))GetProcAddress (
        GetModuleHandle("GetErrorMessageLib.dll"),
        "GetErrorMessageW"
    );        

    GetErrorMessageA(33, result1, sizeof(result1));
    GetErrorMessageW(33, result2, sizeof(result2));

    puts(result1);
    _putws(result2);

    return 0;
}

regulärer Anwendungsfall (setzt voraus, dass der Fehlercode gültig ist, andernfalls ist eine Überprüfung der Rückgabe -1 erforderlich):

#include <stdio.h>
#include "GetErrorMessage.h"
#include <stdio.h>

int main(int argc, char **argv)
{
    char result1[260];
    wchar_t result2[260];

    GetErrorMessageA(33, result1, sizeof(result1));
    puts(result1);

    GetErrorMessageW(33, result2, sizeof(result2));
    _putws(result2);

    return 0;
}

Beispiel für die Verwendung mit Assembly Gnu wie in MinGW32 (wiederum vorausgesetzt, dass der Fehlercode gültig ist, andernfalls ist eine Überprüfung von -1 erforderlich).

    .global _WinMain@16

    .section .text
_WinMain@16:
    // eax = LoadLibraryA("GetErrorMessageLib.dll")
    push $sz0
    call _LoadLibraryA@4 // stdcall, no cleanup needed

    // eax = GetProcAddress(eax, "GetErrorMessageW")
    push $sz1
    push %eax
    call _GetProcAddress@8 // stdcall, no cleanup needed

    // (*eax)(errorCode, szErrorMessage)
    push $200
    push $szErrorMessage
    push errorCode       
    call *%eax // cdecl, cleanup needed
    add $12, %esp

    push $szErrorMessage
    call __putws // cdecl, cleanup needed
    add $4, %esp

    ret $16

    .section .rodata
sz0: .asciz "GetErrorMessageLib.dll"    
sz1: .asciz "GetErrorMessageW"
errorCode: .long 33

    .section .data
szErrorMessage: .space 200

Ergebnis: The process cannot access the file because another process has locked a portion of the file.


1
Dies fügt wirklich nichts Nützliches hinzu. Darüber hinaus ruft es die ANSI-Version von FormatMessageohne ersichtlichen Grund auf und beschränkt sich willkürlich auf 80 Zeichen, wiederum ohne jeglichen Grund. Ich fürchte, das ist nicht hilfreich.
Unsichtbar

Sie haben Recht, ich hatte gehofft, niemand würde etwas über das Fehlen der Unicode-Version bemerken. Ich werde überprüfen, wie eine Unicode-Zeichenfolge in gnu als definiert wird, und meine Lösung ändern. Entschuldigung für die Unehrlichkeit.
Dmitry

ok Unicode-Version ist aktiv. und es ist kein willkürlicher Grund; Alle Fehlermeldungen bestehen entweder aus weniger als 80 Zeichen oder sind nicht lesenswert. Der Fehlercode ist wichtiger als die Fehlermeldung. Es gibt keine Standardfehlermeldungen mit mehr als 80 Zeichen, daher ist dies eine sichere Annahme, und wenn dies nicht der Fall ist, geht kein Speicher verloren.
Dmitry

3
"Alle Fehlermeldungen sind entweder weniger als 80 Zeichen oder nicht lesenswert" - Das ist falsch. Die Fehlermeldung für ERROR_LOCK_VIOLATION(33) lautet: "Der Prozess kann nicht auf die Datei zugreifen, da ein anderer Prozess einen Teil der Datei gesperrt hat." Das ist deutlich länger als 80 Zeichen und sehr lesenswert, wenn Sie versuchen, ein Problem zu lösen und es in einer Diagnoseprotokolldatei zu finden. Diese Antwort bietet keinen wesentlichen Mehrwert gegenüber den vorhandenen Antworten.
IInspectable

Das ist nicht weniger naiv. Es scheitert nur seltener. Mit Ausnahme der Assembly-Implementierung, die sich auf die Puffergröße bezieht (behauptet, Platz für 200 Zeichen zu haben, wenn sie nur Platz für 100 hat). Auch dies fügt nichts Wesentliches hinzu, was in keiner der anderen Antworten bereits enthalten ist. Insbesondere ist es schlimmer als diese Antwort . Sehen Sie sich dort die Aufzählungsliste an und beobachten Sie, welche Probleme Ihre vorgeschlagene Implementierung zusätzlich zu den gerade erwähnten hat.
Unsichtbarer
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.