Come ottenere il messaggio di errore dal codice di errore restituito da GetLastError ()?


138

Dopo una chiamata all'API di Windows, come posso ottenere l'ultimo messaggio di errore in forma testuale?

GetLastError() restituisce un valore intero, non un messaggio di testo.


esiste una ricerca degli errori exe nella sezione degli strumenti in Visual Studio che lo fa abbastanza bene quando hai solo bisogno di un messaggio di errore per il debug.
ColdCat

@ColdCat: per il debug è molto più semplice aggiungere semplicemente un @err,hrorologio e fare in modo che il debugger converta automaticamente l'ultimo codice di errore in una rappresentazione leggibile dall'uomo. L' identificatore di,hr formato funziona per qualsiasi espressione che valuti un valore integrale, ad esempio un 5,hrorologio visualizzerà "ERROR_ACCESS_DENIED: accesso negato". .
Indispensabile l'

2
Dalla GetLastError()documentazione: " Per ottenere una stringa di errore per i codici di errore di sistema, utilizzare la FormatMessage()funzione ". Vedere l'esempio Recupero del codice dell'ultimo errore su MSDN.
Remy Lebeau,

Risposte:


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
Credo che (LPSTR)&messageBufferin questo caso sia necessario passare , poiché altrimenti non è possibile che FormatMessageA possa modificare il suo valore in modo che punti al buffer allocato.
Kylotan,

2
Oh, wow, sì, è un po 'strano. Come modifica il puntatore? Ma passandogli l'indirizzo del puntatore (pointer-to-a-pointer), ma lanciandolo su un puntatore normale ... Stranezza Win32. Grazie per l'attenzione, risolto nella mia base di codice (e la mia risposta). Cattura molto sottile.
Jamin Gray,

1
Grazie mille, il tuo esempio è molto più chiaro di quello di MSDN. Inoltre, quello di MSDN non è stato nemmeno compilato. Include alcune strsafe.hintestazioni, che non sono affatto sicure , causando un sacco di errori del compilatore in winuser.he winbase.h.
Ciao Angelo

2
Alcuni ID errore non sono supportati. Ad esempio, 0x2EE7, ERROR_INTERNET_NAME_NOT_RESOLVED provoca un nuovo errore quando si chiama FormatMessage: 0x13D, il sistema non è in grado di trovare il testo del messaggio numero 0x% 1 nel file messaggi per% 2.
Brent,

3
Problemi con questa implementazione: 1 GetLastErrorè potenzialmente chiamato troppo tardi. 2Nessun supporto Unicode. 3Uso delle eccezioni senza implementare garanzie di sicurezza delle eccezioni.
Indispensabile

65

Aggiornato (11/2017) per prendere in considerazione alcuni commenti.

Esempio semplice:

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 - L'esempio presuppone che tu stia compilando con UNICODE definito. 'FormatMessage' è in realtà una macro che si espande in 'FormatMessageA' per i buffer di caratteri Ansi / MBCS o 'FormatMessageW' per i buffer UTF16 / UNICODE, a seconda della modalità di compilazione dell'applicazione. Mi sono preso la libertà di modificare l'esempio sopra per invocare esplicitamente la versione che corrisponde al tipo di buffer di output (wchar_t).
Acquista

2
Aggiungi FORMAT_MESSAGE_IGNORE_INSERTS: "Se non hai il controllo della stringa di formato, devi passare FORMAT_MESSAGE_IGNORE_INSERTS per evitare che% 1 causi problemi." blogs.msdn.microsoft.com/oldnewthing/20071128-00/?p=24353
Roi Danton

1
Problemi con questa implementazione: 1impossibile specificare il FORMAT_MESSAGE_IGNORE_INSERTSflag importante . 2 GetLastErrorpotenzialmente chiamato troppo tardi. 3Limitazione arbitraria del messaggio a 256 unità di codice. 4Nessuna gestione degli errori.
Indispensabile l'

1
sizeof (buf) dovrebbe essere ARRAYSIZE (buf) poiché FormatMessage prevede la dimensione del buffer in TCHAR, non in byte.
Kai

1
Non possiamo usare 256invece di (sizeof(buf) / sizeof(wchar_t)? è accettabile e sicuro?
BattleTested



14

GetLastError restituisce un codice numerico di errore. Per ottenere un messaggio di errore descrittivo (ad esempio, da visualizzare a un utente), è possibile chiamare FormatMessage :

// 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 ++, puoi semplificare considerevolmente l'interfaccia usando la classe std :: string:

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

NOTA: queste funzioni funzionano anche per i valori HRESULT. Basta cambiare il primo parametro da DWORD dwErrorCode in HRESULT hResult. Il resto del codice può rimanere invariato.


Queste implementazioni offrono i seguenti miglioramenti rispetto alle risposte esistenti:

  • Codice di esempio completo, non solo un riferimento all'API da chiamare.
  • Fornisce implementazioni in C e C ++.
  • Funziona con le impostazioni del progetto Unicode e MBCS.
  • Prende il codice di errore come parametro di input. Questo è importante, poiché l'ultimo codice di errore di un thread è valido solo in punti ben definiti. Un parametro di input consente al chiamante di seguire il contratto documentato.
  • Implementa un'adeguata sicurezza delle eccezioni. A differenza di tutte le altre soluzioni che utilizzano implicitamente le eccezioni, questa implementazione non perderà memoria nel caso in cui venga generata un'eccezione durante la costruzione del valore restituito.
  • Uso corretto della FORMAT_MESSAGE_IGNORE_INSERTSbandiera. Vedi L'importanza del flag FORMAT_MESSAGE_IGNORE_INSERTS per ulteriori informazioni.
  • Gestione corretta degli errori / segnalazione degli errori, a differenza di alcune delle altre risposte, che ignorano silenziosamente gli errori.


Questa risposta è stata incorporata nella documentazione di Stack Overflow. I seguenti utenti hanno contribuito all'esempio: stackptr , Ajay , Cody Grey ♦ , IInspectable .


1
Invece di lanciare std::runtime_error, suggerisco di lanciare std::system_error(lastError, std::system_category(), "Failed to retrieve error message string.")dove lastErrorsarebbe il valore restituito GetLastError()dopo la FormatMessage()chiamata fallita .
zett42,

Qual è il vantaggio di utilizzare la tua versione C FormatMessagedirettamente?
Micha Wiedenmann,

@mic: È come chiedere: "Qual è il vantaggio delle funzioni in C?" Le funzioni riducono la complessità fornendo astrazioni. In questo caso, l'astrazione riduce il numero di parametri da 7 a 3, implementa il contratto documentato dell'API passando flag e parametri compatibili e codifica la logica di segnalazione degli errori documentata. Fornisce un'interfaccia concisa, con semantica facile da indovinare, risparmiando la necessità di investire 11 minuti per leggere la documentazione .
Indispensabile il

Mi piace questa risposta, è molto chiaro che è stata prestata molta attenzione alla correttezza del codice. Ho due domande 1) Perché usi ::HeapFree(::GetProcessHeap(), 0, p)il deleter anziché ::LocalFree(p)come suggerisce la documentazione? 2) Mi rendo conto che la documentazione dice di farlo in questo modo, ma non reinterpret_cast<LPTSTR>(&psz)viola la rigida regola di aliasing?
giovedì

@teh: grazie per il feedback. Domanda 1): Onestamente, non so se sia sicuro. Non ricordo chi o perché sia HeapFreestata introdotta la chiamata , mentre questo era ancora un argomento nella documentazione SO. Dovrò indagare (anche se la documentazione sembra essere chiara, questo non è sicuro). Domanda 2): questo è duplice. Per una configurazione MBCS, LPTSTRè un alias per char*. Quel cast è sempre sicuro. Per una configurazione Unicode, eseguire il casting su wchar_t*è comunque difficile. Non so se questo è sicuro in C, ma molto probabilmente non è in C ++. Dovrei indagare anche su questo.
Indispensabile il

13

In generale, è necessario utilizzare FormatMessage per convertire da un codice di errore Win32 in testo.

Dalla documentazione MSDN :

Formatta una stringa di messaggio. La funzione richiede una definizione del messaggio come input. La definizione del messaggio può provenire da un buffer passato nella funzione. Può provenire da una risorsa della tabella dei messaggi in un modulo già caricato. Oppure il chiamante può chiedere alla funzione di cercare le risorse della tabella dei messaggi del sistema per la definizione del messaggio. La funzione trova la definizione del messaggio in una risorsa della tabella dei messaggi in base a un identificatore del messaggio e un identificatore della lingua. La funzione copia il testo del messaggio formattato in un buffer di output, elaborando eventuali sequenze di inserimento incorporate, se richiesto.

La dichiarazione di 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

Se stai usando c # puoi usare questo codice:

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

Ho confermato che questo codice funziona. TS non dovrebbe accettare questa risposta?
swdev,

2
Se è necessario per un ulteriore lancio, c'è un modo più semplice per farlo in C # con Win32Exception
SerG

2
@swdev: Perché qualcuno dovrebbe accettare una risposta in C # a una domanda taggata c o c ++ ? Allo stato attuale, questa risposta non risponde nemmeno alla domanda posta.
Indispensabile l'

1
Bene, non ricordo di aver votato su questa risposta proposta. Ho risolto l'evidente difetto nella logica di @ swdev. Ma dal momento che non mi crederai, ora te lo dimostrerò: ecco un altro voto negativo. Quello è da parte mia, perché questa risposta - sebbene possa essere utile a una domanda diversa - semplicemente non è utile data la domanda che è stata posta.
Indispensabile l'

2
"Immagino che tu abbia utili spunti da offrire oltre l'ovvio" - Davvero, ho .
Indispensabile l'

4

Se è necessario supportare MBCS e Unicode, la risposta di Mr.C64 non è abbastanza. Il buffer deve essere dichiarato TCHAR e trasmesso a LPTSTR. Si noti che questo codice non si occupa della fastidiosa newline che Microsoft aggiunge al messaggio di errore.

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

Inoltre, per brevità trovo utile il seguente metodo:

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

Nel caso in cui il CStringc'tor generi un'eccezione, questa implementazione perde la memoria allocata dalla chiamata FormatMessage.
Indispensabile

Vero, ma ho usato questo codice per molti anni e non è mai stato un problema. L'unico caso in cui è probabile che il ctor CString lanci è la mancata allocazione della memoria e la maggior parte del codice MFC, inclusi i contenuti forniti da Microsoft, non gestisce le condizioni di memoria insufficiente come si potrebbe desiderare. Fortunatamente la maggior parte dei PC ora ha così tanta memoria che devi lavorare piuttosto duramente per usarlo tutto. Qualsiasi utilizzo che generi un'istanza CString temporanea (inclusa la restituzione di CString) comporta questo rischio. È un compromesso rischio / convenienza. Inoltre, se si verifica in un gestore di messaggi, verrà rilevata l'eccezione.
victimofleisure

Gran parte di questo commento è sbagliato, scusa. "Non mi è mai successo" è un punto dannatamente debole da sottolineare, specialmente quando sai come il codice può fallire. La quantità di memoria non ha alcun impatto sullo spazio degli indirizzi disponibile assegnato a un processo. La RAM è solo un'ottimizzazione delle prestazioni. Copy-elision impedisce l'assegnazione di un temporaneo, quando il codice viene scritto per consentire NRVO. La presenza o meno di eccezioni in un gestore messaggi dipende dal testimone del processo e dalle impostazioni esterne. Ho inviato una risposta che indica che la gestione del rischio non comporta inconvenienti.
Indispensabile il

Inoltre, il rischio di rimanere senza memoria nella costruzione di un temporaneo è controverso. A quel punto tutte le risorse sono state liberate e non ne verrà fuori nulla di male.
Indispensabile il

1
No, mi dispiace che il codice non ti sia utile. Ma TBH è piuttosto basso nella mia lista di problemi.
victimofleisure

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

Potresti anche aggiungere qualche spiegazione?
Robert,

1
Problemi con questa implementazione: 1nessun supporto Unicode. 2Formattazione inappropriata del messaggio di errore. Se il chiamante deve elaborare la stringa restituita, può semplicemente farlo. L'implementazione lascia il chiamante senza alcuna opzione. 3Uso delle eccezioni ma mancanza di un'adeguata sicurezza delle eccezioni. Nel caso in cui gli std::stringoperatori generino eccezioni, il buffer allocato da FormatMessageviene trapelato. 4Perché non semplicemente restituire a std::stringinvece di fare in modo che il chiamante passi un oggetto per riferimento?
Indispensabile l'

1

Da c ++ 11, è possibile utilizzare la libreria standard anziché 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);
    }
}

C'è solo una piccola finestra in cui la chiamata GetLastErrorproduce un risultato significativo. Essendo questo C ++, l'unica opzione sicura qui è che il chiamante fornisca l'ultimo codice di errore. Certamente non aiuta, che il codice presentato chiama GetLastError due volte . Inoltre, sebbene conveniente, la mancanza intrinseca di C ++ di supporto per caratteri ampi non rende l' error_categoryinterfaccia universalmente utile. Ciò si aggiunge alla lunga storia di opportunità perse del C ++.
Probabile

Un buon punto sulla chiamata inutile a GetLastError. Ma non vedo alcuna differenza tra la chiamata GetLastErrorqui o il chiamante. Per quanto riguarda C ++ e wchar: non abbandonare la speranza, Microsoft sta iniziando a consentire alle app di essere solo UTF-8 .
Cronologico

"Non vedo alcuna differenza" - Si consideri il seguente sito chiamata: log_error("error", GetLastErrorAsString());. Considera anche che log_erroril primo argomento è di tipo std::string. La chiamata di conversione (invisibile) ha appena lasciato cadere le tue garanzie per acquisire un valore significativo dal GetLastErrormomento in cui lo stai chiamando.
Probabile

Perché pensi che il costruttore std :: string chiama una funzione Win32?
Cronologico

1
mallocrichiede HeapAllocl'heap di processo. L'heap di processo è coltivabile. Se ha bisogno di crescere, in ultima analisi, chiamata VirtualAlloc , che non stabilito lo scorso codice di errore del thread chiamante. Ora questo è totalmente privo del punto, ovvero: C ++ è un campo minato. Questa implementazione si aggiunge a ciò, fornendo un'interfaccia con garanzie implicite a cui semplicemente non è all'altezza. Se ritieni che non ci siano problemi, dovrebbe essere facile per te provare la correttezza della soluzione proposta. In bocca al lupo.
Probabile

0

Lascio questo qui poiché dovrò usarlo più tardi. È una fonte per un piccolo strumento binario compatibile che funzionerà ugualmente bene in assembly, C e C ++.

GetErrorMessageLib.c (compilato per 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;
    }
}

versione incorporata (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 */

caso d'uso dinamico (presupponendo che il codice di errore sia valido, altrimenti è necessario un controllo -1):

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

caso d'uso regolare (presuppone che il codice di errore sia valido, altrimenti è necessario -1 controllo di ritorno):

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

esempio usando con assembly gnu come in MinGW32 (di nuovo, supposto che il codice di errore sia valido, altrimenti è necessario un controllo -1).

    .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

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


1
Questo in realtà non aggiunge nulla di utile. Inoltre, chiama la versione ANSI di FormatMessage, senza una ragione apparente, e si limita arbitrariamente a 80 caratteri, ancora una volta, per nessuna ragione. Temo che questo non sia utile.
Indispensabile il

hai ragione speravo che nessuno si accorgesse della mancanza della versione Unicode. Controllerò come definire una stringa Unicode in gnu come e come modificare la mia soluzione. scusa per la disonestà.
Dmitry,

ok la versione unicode è attiva. e non è una ragione arbitraria; tutti i messaggi di errore possono contenere meno di 80 caratteri o non vale la pena leggerli e il codice di errore è più importante del messaggio di errore. Non ci sono messaggi di errore standard che superano gli 80 caratteri, quindi è un presupposto sicuro e, quando non lo è, non perde la memoria.
Dmitry,

3
"tutti i messaggi di errore hanno una lunghezza inferiore a 80 caratteri o non vale la pena leggerli" - È sbagliato. Il messaggio di errore per ERROR_LOCK_VIOLATION(33) è: "Il processo non può accedere al file perché un altro processo ha bloccato una parte del file." È chiaramente più lungo di 80 caratteri e vale la pena leggere se stai cercando di risolvere un problema e trovarlo in un file di registro diagnostico. Questa risposta non aggiunge alcun valore sostanziale alle risposte esistenti.
Indispensabile il

Questo non è meno ingenuo. Semplicemente fallisce meno spesso. Tranne l'implementazione dell'assembly che si trova sulla dimensione del buffer (affermando di avere spazio per 200 caratteri, quando ha spazio solo per 100). Ancora una volta, questo non aggiunge nulla di sostanziale, che non è già presente in nessuna delle altre risposte. In particolare, è peggio di questa risposta . Vedi la lista dei proiettili e osserva quali sono i problemi della tua implementazione proposta, oltre a quelli che ho appena sottolineato.
Indispensabile il
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.