Como obter a mensagem de erro do código de erro retornado por GetLastError ()?

138

Após uma chamada da API do Windows, como posso receber a última mensagem de erro em forma de texto?

GetLastError() retorna um valor inteiro, não uma mensagem de texto.

Micha Wiedenmann
fonte
costumava haver uma pesquisa de erro exe na seção de ferramentas do visual studio, que faz isso muito bem quando você só precisa de uma mensagem de erro para depuração.
ColdCat 11/01
@ColdCat: Para a depuração, é muito mais fácil adicionar um @err,hrrelógio e fazer com que o depurador converta automaticamente o último código de erro em uma representação legível por humanos. O ,hrespecificador de formato funciona para qualquer expressão que seja avaliada como um valor integral, por exemplo, um 5,hrrelógio exibirá "ERROR_ACCESS_DENIED: acesso negado". .
IInspectable
2
Na GetLastError()documentação: " Para obter uma sequência de erros para códigos de erro do sistema, use a FormatMessage()função. ". Consulte o exemplo Recuperando o código do último erro no MSDN.
Remy Lebeau 29/11

Respostas:

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;
}
Jamin Gray
fonte
2
Eu acredito que você realmente precisa passar (LPSTR)&messageBufferneste caso, caso contrário, não há como o FormatMessageA alterar seu valor para apontar para o buffer alocado.
Kylotan
2
Oh, uau, sim, isso é meio estranho. Como isso modificaria o ponteiro? Mas passar o endereço do ponteiro (ponteiro para um ponteiro), mas convertê-lo em um ponteiro regular ... Estranho no Win32. Obrigado pelo aviso, consertei na minha própria base de código (e na minha resposta). Captura muito sutil.
Jamin Grey
1
Muito obrigado, seu exemplo é muito mais claro que o do MSDN. Mais ainda, o do MSDN nem conseguiu compilar. Inclui algum strsafe.hcabeçalho, que não é seguro , causando muitos erros de compilador no winuser.he winbase.h.
Hi-Angel
2
Alguns IDs de erro não são suportados. Por exemplo, 0x2EE7, ERROR_INTERNET_NAME_NOT_RESOLVED causa um novo erro ao chamar FormatMessage: 0x13D. O sistema não pode encontrar o texto da mensagem para o número da mensagem 0x% 1 no arquivo de% 2.
Brent
3
Problemas com esta implementação: 1 GetLastErroré potencialmente chamado tarde demais. 2Não há suporte para Unicode. 3Uso de exceções sem implementar garantias de segurança de exceções.
IInspectable
65

Atualizado (11/2017) para levar em consideração alguns comentários.

Exemplo fácil:

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);
LeviX
fonte
3
@ Hi-Angel - O exemplo assume que você está compilando com o UNICODE definido. 'FormatMessage' é na verdade uma macro que se expande para 'FormatMessageA' para buffers de caracteres Ansi / MBCS ou 'FormatMessageW' para buffers UTF16 / UNICODE, dependendo de como o aplicativo é compilado. Tomei a liberdade de editar o exemplo acima para chamar explicitamente a versão que corresponde ao tipo de buffer de saída (wchar_t).
Bukes
2
Adicione FORMAT_MESSAGE_IGNORE_INSERTS: "Se você não está no controle da cadeia de formatação, deve passar FORMAT_MESSAGE_IGNORE_INSERTS para impedir que o% 1 cause problemas." blogs.msdn.microsoft.com/oldnewthing/20071128-00/?p=24353
Roi Danton
1
Problemas com esta implementação: 1Falha ao especificar o FORMAT_MESSAGE_IGNORE_INSERTSsinalizador importante . 2 GetLastErrorpotencialmente chamado tarde demais. 3Restrição arbitrária da mensagem para 256 unidades de código. 4Sem manipulação de erros.
IInspectable
1
sizeof (buf) deve ser ARRAYSIZE (buf), pois FormatMessage espera o tamanho do buffer em TCHARs, não em bytes.
Kai
1
Não podemos usar em 256vez de (sizeof(buf) / sizeof(wchar_t)? aceitável e seguro?
BattleTested
14

FormatMessage irá transformar o retorno inteiro de GetLastError em uma mensagem de texto.

Jonathan Graehl
fonte
14

GetLastError retorna um código de erro numérico. Para obter uma mensagem de erro descritiva (por exemplo, para exibir para um usuário), você pode chamar 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);
}

No C ++, você pode simplificar consideravelmente a interface usando a 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: Essas funções também funcionam para valores HRESULT. Apenas mude o primeiro parâmetro de DWORD dwErrorCode para HRESULT hResult. O restante do código pode permanecer inalterado.


Essas implementações fornecem as seguintes melhorias em relação às respostas existentes:

  • Código de amostra completo, não apenas uma referência à API a ser chamada.
  • Fornece implementações em C e C ++.
  • Funciona para configurações de projeto Unicode e MBCS.
  • Toma o código de erro como um parâmetro de entrada. Isso é importante, pois o último código de erro de um encadeamento é válido apenas em pontos bem definidos. Um parâmetro de entrada permite que o chamador siga o contrato documentado.
  • Implementa a segurança de exceção adequada. Ao contrário de todas as outras soluções que usam implicitamente exceções, essa implementação não vazará memória no caso de uma exceção ser lançada ao construir o valor de retorno.
  • Uso adequado da FORMAT_MESSAGE_IGNORE_INSERTSbandeira. Consulte A importância do sinalizador FORMAT_MESSAGE_IGNORE_INSERTS para obter mais informações.
  • Tratamento adequado de erros / relatório de erros, ao contrário de outras respostas, que ignoram silenciosamente os erros.


Esta resposta foi incorporada da Stack Overflow Documentation. Os seguintes usuários contribuíram para o exemplo: stackptr , Ajay , Cody Gray ♦ , IInspectable .

IIspectável
fonte
1
Em vez de jogar std::runtime_error, sugiro jogar para std::system_error(lastError, std::system_category(), "Failed to retrieve error message string.")onde lastErrorseria o valor de retorno GetLastError()após a falha na FormatMessage()chamada.
Zett42 4/17
Qual é a vantagem de usar sua versão C FormatMessagediretamente?
Micha Wiedenmann
@mic: Isso é como perguntar: "Qual é a vantagem das funções em C?" As funções reduzem a complexidade fornecendo abstrações. Nesse caso, a abstração reduz o número de parâmetros de 7 para 3, implementa o contrato documentado da API passando sinalizadores e parâmetros compatíveis e codifica a lógica do relatório de erros documentado. Ele fornece uma interface concisa, com semântica fácil de adivinhar, poupando a necessidade de investir 11 minutos para ler a documentação .
11nspectable
Gosto dessa resposta, é muito claro que foi dada muita atenção à correção do código. Eu tenho duas perguntas. 1) Por que você usa ::HeapFree(::GetProcessHeap(), 0, p)o deleter em vez de ::LocalFree(p)como a documentação sugere? 2) Percebo que a documentação diz para fazer dessa maneira, mas não reinterpret_cast<LPTSTR>(&psz)viola a regra estrita de alias?
TeJ JoE
@teh: Obrigado pelo feedback. Pergunta 1): Honestamente, não sei se isso é seguro. Não me lembro de quem ou por que a chamada HeapFreefoi apresentada, enquanto este ainda era um tópico na documentação do SO. Vou ter que investigar (embora a documentação pareça clara, isso não é seguro). Pergunta 2): Isso é duplo. Para uma configuração do MBCS, LPTSTRé um alias para char*. Esse elenco é sempre seguro. Para uma configuração Unicode, converter para wchar_t*é suspeito de qualquer forma. Não sei se isso é seguro em C, mas provavelmente não em C ++. Eu teria que investigar isso também.
2nspectable em 04/08
13

Em geral, você precisa usar FormatMessagepara converter de um código de erro do Win32 em texto.

Na documentação do MSDN :

Formata uma sequência de mensagens. A função requer uma definição de mensagem como entrada. A definição da mensagem pode vir de um buffer passado para a função. Pode vir de um recurso da tabela de mensagens em um módulo já carregado. Ou o chamador pode solicitar à função que procure os recursos da tabela de mensagens do sistema para a definição da mensagem. A função localiza a definição de mensagem em um recurso da tabela de mensagens com base em um identificador de mensagem e um identificador de idioma. A função copia o texto da mensagem formatada em um buffer de saída, processando quaisquer seqüências de inserção incorporadas, se solicitado.

A declaração de 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
);
Vinay Sajip
fonte
7

Se você estiver usando c #, poderá usar este código:

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();
        }
    }
}
rboy
fonte
Confirmei que este código funciona. TS não deveria aceitar esta resposta?
swdev
2
Se for necessário por mais um arremesso há uma maneira mais simples de fazê-lo em C # com Win32Exception
Serg
2
@swdev: Por que alguém deveria aceitar uma resposta em c # para uma pergunta marcada c ou c ++ ? Tal como está, esta resposta nem sequer aborda a pergunta que está sendo feita.
IInspectable
1
Bem, não me lembro de ter votado nesta resposta proposta. Abordei a falha óbvia na lógica do @ swdev. Mas como você não vai acreditar em mim, agora vou provar para você: Aqui, faça outra votação negativa. Essa é minha, porque essa resposta - embora possa ser útil dada uma pergunta diferente - simplesmente não é útil, dada a pergunta que estava sendo feita.
IInspectable
2
"Eu acho que você tem idéias úteis a oferecer além do óbvio" - Na verdade, eu tenho .
IInspectable
4

Se você precisa oferecer suporte ao MBCS e ao Unicode, a resposta do Sr. C64 não é suficiente. O buffer deve ser declarado TCHAR e convertido para LPTSTR. Observe que esse código não lida com a nova linha irritante que a Microsoft anexa à mensagem de erro.

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

Além disso, por uma questão de brevidade, acho útil o seguinte método:

CString GetLastErrorString()
{
    return FormatErrorMessage(GetLastError());
}
vítima
fonte
Caso o CStringc'tor gere uma exceção, essa implementação vazará a memória alocada pela chamada para FormatMessage.
IInspectable
É verdade, mas eu uso esse código há muitos anos e nunca foi um problema. O único caso em que o CString é suscetível de ser lançado é a falha na alocação de memória, e a maioria dos códigos MFC - incluindo os itens fornecidos pela Microsoft - não lida com as condições de falta de memória da maneira mais agradável possível. Felizmente, a maioria dos PCs agora tem tanta memória que você precisa trabalhar bastante para usar tudo. Qualquer uso que gera uma instância temporária do CString (incluindo o retorno de um CString) corre esse risco. É uma troca de risco / conveniência. Além disso, se isso ocorrer em um manipulador de mensagens, a exceção será capturada.
victimofleisure
A maior parte deste comentário está errada, desculpe. "Isso nunca aconteceu comigo" é um ponto fraco, especialmente quando você sabe como o código pode falhar. A quantidade de memória também não afeta o espaço de endereço disponível alocado para um processo. RAM é apenas uma otimização de desempenho. A eliminação de cópias evita a alocação temporária, quando o código é gravado para permitir a NRVO. O fato de as exceções serem capturadas ou não em um manipulador de mensagens depende da configuração do processo e das configurações externas. Enviei uma resposta que mostra que o gerenciamento de riscos não causa transtornos.
11nspectable
Além disso, o risco de ficar sem memória na construção de um temporário é discutível. Nesse ponto, todos os recursos foram liberados e nada de ruim resultará disso.
11nspectable
1
Não, desculpe o código não ser útil para você. Mas TBH é muito baixo na minha lista de problemas.
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);
}
}
Andriy Kuz
fonte
Você também pode adicionar alguma explicação?
Robert
1
Problemas com esta implementação: 1Não há suporte para Unicode. 2Formatação inadequada da mensagem de erro. Se o chamador precisar processar a sequência retornada, poderá fazê-lo. Sua implementação deixa o chamador sem opção. 3Uso de exceções, mas falta de segurança adequada para exceções. Caso os std::stringoperadores lançem exceções, o buffer alocado por FormatMessageé vazado. 4Por que não simplesmente retornar a em std::stringvez de fazer com que o chamador passe um objeto por referência?
IInspectable
1

Desde o c ++ 11, você pode usar a biblioteca padrão em vez de 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);
    }
}
Chronial
fonte
Há apenas uma pequena janela em que a chamada GetLastErrorproduz um resultado significativo. Sendo esse C ++, a única opção segura aqui é fazer com que o chamador forneça o último código de erro. Certamente não ajuda, que o código apresentado chama GetLastError duas vezes . Além disso, embora conveniente, a falta inerente de amplo suporte a caracteres do C ++ falha ao tornar a error_categoryinterface universalmente útil. Isso apenas contribui para o longo histórico de oportunidades perdidas do C ++.
IInspectable
Bom ponto sobre a chamada inútil para GetLastError. Mas não vejo diferença entre ligar GetLastErroraqui ou fazer com que o chamador ligue. Em relação ao C ++ e ao wchar: não abandone a esperança, a Microsoft está começando a permitir que os aplicativos sejam apenas UTF-8 .
Chronial
"Eu não vejo nenhuma diferença" - Considere o seguinte site de chamada: log_error("error", GetLastErrorAsString());. Considere também que log_erroro primeiro argumento desse tipo é do tipo std::string. A chamada de invisibilidade (conversão invisível) acabou de abandonar suas garantias para capturar um valor significativo GetLastErrorno momento em que você está chamando.
IInspectable
Por que você acha que o construtor std :: string chama uma função Win32?
Chronial
1
mallocsolicita HeapAlloco heap do processo. O heap do processo é expansível. Se ele precisa para crescer, ele acabará por chamar VirtualAlloc , que não definida último código de erro do segmento de chamada. Agora isso está totalmente errado, o que é: C ++ é um campo minado. Essa implementação apenas contribui para isso, fornecendo uma interface com garantias implícitas que ela simplesmente não pode cumprir. Se você acredita que não há problema, deve ser fácil provar a correção da solução proposta. Boa sorte.
IInspectable
0

vou deixar isso aqui, pois precisarei usá-lo mais tarde. É uma fonte para uma pequena ferramenta compatível com binários que funcionará igualmente bem em montagem, C e C ++.

GetErrorMessageLib.c (compilado em 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;
    }
}

versão embutida (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 de uso dinâmico (assumindo que o código de erro é válido, caso contrário, é necessária uma verificação -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 de uso regular (assume que o código de erro é válido, caso contrário, é necessária uma verificação de retorno -1):

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

exemplo usando with assembly gnu como no MinGW32 (novamente, assumindo que o código de erro é válido, caso contrário, é necessária uma verificação -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

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

Dmitry
fonte
1
Isso realmente não adiciona nada de útil. Além disso, ele chama a versão ANSI de FormatMessage, sem motivo aparente, e se limita arbitrariamente a 80 caracteres, novamente, por nenhum motivo. Receio, isso não ajuda.
IInspectable
você está certo, eu esperava que ninguém notasse a falta da versão Unicode. Vou verificar como definir uma string Unicode no gnu e modificar minha solução. desculpe pela desonestidade.
Dmitry
ok versão unicode está pronta. e não é uma razão arbitrária; todas as mensagens de erro têm menos de 80 caracteres ou não valem a pena ler, e o código de erro é mais importante que a mensagem de erro. Não há mensagens de erro padrão que excedam 80 caracteres, portanto é uma suposição segura e, quando não é, não perde memória.
Dmitry
3
"todas as mensagens de erro têm menos de 80 caracteres ou não valem a pena ler" - Isso está errado. A mensagem de erro para ERROR_LOCK_VIOLATION(33) é: "O processo não pode acessar o arquivo porque outro processo bloqueou uma parte do arquivo". São ambos claramente maiores que 80 caracteres e vale muito a pena ler, se você estiver tentando resolver um problema e encontrar isso em um arquivo de log de diagnóstico. Esta resposta não agrega nenhum valor substancial sobre as respostas existentes.
IInspectable
Isso não é menos ingênuo. Apenas falha com menos frequência. Exceto pela implementação da montagem que se refere ao tamanho do buffer (alegando ter espaço para 200 caracteres, quando só tem espaço para 100). Novamente, isso não adiciona nada de substancial, isso ainda não está em nenhuma das outras respostas. Em particular, é pior do que esta resposta . Veja a lista de marcadores e observe quais problemas sua implementação proposta possui, além dos que acabei de apontar.
11nspectable