Comment obtenir le message d'erreur du code d'erreur renvoyé par GetLastError ()?


138

Après un appel à l'API Windows, comment puis-je obtenir le dernier message d'erreur sous forme textuelle?

GetLastError() renvoie une valeur entière, pas un message texte.


il y avait une recherche d'erreur exe dans la section outil de Visual Studio, ce qui le fait assez bien lorsque vous n'avez besoin que d'un message d'erreur pour le débogage.
ColdCat

@ColdCat: Pour le débogage, il est beaucoup plus facile d'ajouter simplement une @err,hrmontre et de demander au débogueur de convertir automatiquement le dernier code d'erreur en une représentation lisible par l'homme. Le ,hrspécificateur de format fonctionne pour toute expression qui s'évalue à une valeur intégrale, par exemple une 5,hrmontre affichera "ERROR_ACCESS_DENIED: Accès refusé." .
IInspectable

2
Dans la GetLastError()documentation: " Pour obtenir une chaîne d'erreur pour les codes d'erreur système, utilisez la FormatMessage()fonction. ". Consultez l'exemple de récupération du dernier code d'erreur sur MSDN.
Remy Lebeau

Réponses:


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
Je pense que vous devez en fait passer (LPSTR)&messageBufferdans ce cas, car sinon, FormatMessageA ne pourrait pas modifier sa valeur pour pointer vers le tampon alloué.
Kylotan

2
Oh, wow, ouais c'est un peu bizarre. Comment modifierait-il le pointeur? Mais en lui passant l'adresse du pointeur (pointeur vers un pointeur), mais en le convertissant en un pointeur normal ... Bizarrerie Win32. Merci pour la tête haute, je l'ai corrigé dans ma propre base de code (et ma réponse). Prise très subtile.
Jamin Gray

1
Merci beaucoup, votre exemple est beaucoup plus clair que celui de MSDN. De plus, celui de MSDN n'a même pas pu compiler. Il comprend un en- strsafe.htête, qui n'est pas du tout sûr , cela provoque un tas d'erreurs de compilation dans winuser.het winbase.h.
Hi-Angel

2
Certains ID d'erreur ne sont pas pris en charge. Par exemple, 0x2EE7, ERROR_INTERNET_NAME_NOT_RESOLVED provoque une nouvelle erreur lors de l'appel de FormatMessage: 0x13D, le système ne peut pas trouver le texte du message pour le numéro de message 0x% 1 dans le fichier de messages pour% 2.
Brent

3
Problèmes avec cette implémentation: 1 GetLastErrorest potentiellement appelée trop tard. 2Pas de support Unicode. 3Utilisation d'exceptions sans mettre en œuvre des garanties de sécurité d'exception.
IInspectable

65

Mis à jour (11/2017) pour prendre en considération certains commentaires.

Exemple simple:

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'exemple suppose que vous compilez avec UNICODE défini. «FormatMessage» est en fait une macro qui se développe soit en «FormatMessageA» pour les tampons de caractères Ansi / MBCS, soit en «FormatMessageW» pour les tampons UTF16 / UNICODE, selon la manière dont l'application est compilée. J'ai pris la liberté d'éditer l'exemple ci-dessus pour invoquer explicitement la version qui correspond au type de tampon de sortie (wchar_t).
Bukes

2
Ajouter FORMAT_MESSAGE_IGNORE_INSERTS: "Si vous ne contrôlez pas la chaîne de format, vous devez passer FORMAT_MESSAGE_IGNORE_INSERTS pour éviter que% 1 ne cause des problèmes." blogs.msdn.microsoft.com/oldnewthing/20071128-00/?p=24353
Roi Danton

1
Problèmes avec cette implémentation: 1échec de la spécification de l' FORMAT_MESSAGE_IGNORE_INSERTSindicateur important . 2 GetLastErrorpotentiellement appelé trop tard. 3Restriction arbitraire du message à 256 unités de code. 4Aucune gestion des erreurs.
IInspectable

1
sizeof (buf) doit être ARRAYSIZE (buf) car FormatMessage attend la taille du tampon en TCHAR, pas en octets.
Kai

1
Ne pouvons-nous pas utiliser 256au lieu de (sizeof(buf) / sizeof(wchar_t)? est-ce acceptable et sûr?
BattleTested



14

GetLastError renvoie un code d'erreur numérique. Pour obtenir un message d'erreur descriptif (par exemple, à afficher à un utilisateur), vous pouvez appeler 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);
}

En C ++, vous pouvez considérablement simplifier l'interface en utilisant 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.");
    }
}

REMARQUE: ces fonctions fonctionnent également pour les valeurs HRESULT. Changez simplement le premier paramètre de DWORD dwErrorCode à HRESULT hResult. Le reste du code peut rester inchangé.


Ces implémentations apportent les améliorations suivantes par rapport aux réponses existantes:

  • Exemple de code complet, pas seulement une référence à l'API à appeler.
  • Fournit des implémentations C et C ++.
  • Fonctionne pour les paramètres de projet Unicode et MBCS.
  • Prend le code d'erreur comme paramètre d'entrée. Ceci est important, car le dernier code d'erreur d'un thread n'est valide qu'à des points bien définis. Un paramètre d'entrée permet à l'appelant de suivre le contrat documenté.
  • Implémente une sécurité d'exception appropriée. Contrairement à toutes les autres solutions qui utilisent implicitement des exceptions, cette implémentation ne perdra pas de mémoire au cas où une exception serait levée lors de la construction de la valeur de retour.
  • Utilisation correcte du FORMAT_MESSAGE_IGNORE_INSERTSdrapeau. Voir l'importance de l'indicateur FORMAT_MESSAGE_IGNORE_INSERTS pour plus d'informations.
  • Une gestion correcte des erreurs / un rapport d'erreur, contrairement à certaines des autres réponses, qui ignorent silencieusement les erreurs.


Cette réponse a été intégrée à partir de la documentation Stack Overflow. Les utilisateurs suivants ont contribué à l'exemple: stackptr , Ajay , Cody Gray ♦ , IInspectable .


1
Au lieu de lancer std::runtime_error, je suggère de jeter std::system_error(lastError, std::system_category(), "Failed to retrieve error message string.")lastErrorserait la valeur de retour GetLastError()après l'échec de l' FormatMessage()appel.
zett42

Quel est l'avantage d'utiliser FormatMessagedirectement votre version C ?
Micha Wiedenmann

@mic: C'est comme demander: "Quel est l'avantage des fonctions en C?" Les fonctions réduisent la complexité en fournissant des abstractions. Dans ce cas, l'abstraction réduit le nombre de paramètres de 7 à 3, implémente le contrat documenté de l'API en passant des indicateurs et des paramètres compatibles, et encode la logique de rapport d'erreur documentée. Il fournit une interface concise, avec une sémantique facile à deviner, vous évitant d'avoir à investir 11 minutes pour lire la documentation .
IInspectable le

J'aime cette réponse, il est très clair qu'une attention particulière a été portée à l'exactitude du code. J'ai deux questions. 1) Pourquoi utilisez-vous ::HeapFree(::GetProcessHeap(), 0, p)dans le deleter au lieu de ::LocalFree(p)comme le suggère la documentation? 2) Je me rends compte que la documentation dit de le faire de cette façon, mais ne reinterpret_cast<LPTSTR>(&psz)viole pas la règle stricte d'aliasing?
Teh JoE

@teh: Merci pour vos commentaires. Question 1): Honnêtement, je ne sais pas si c'est sûr. Je ne me souviens pas, à qui ou pourquoi l'appel a HeapFreeété lancé, alors que c'était encore un sujet dans la documentation SO. Je vais devoir enquêter (bien que la documentation semble claire, ce n'est pas sûr). Question 2): Ceci est double. Pour une configuration MBCS, LPTSTRest un alias pour char*. Ce casting est toujours en sécurité. Pour une configuration Unicode, lancer en wchar_t*est en tout cas louche. Je ne sais pas si cela est sûr en C, mais ce n'est probablement pas en C ++. Je devrais aussi enquêter là-dessus.
IInspectable

13

En général, vous devez utiliser FormatMessagepour convertir un code d'erreur Win32 en texte.

À partir de la documentation MSDN :

Formate une chaîne de message. La fonction nécessite une définition de message en entrée. La définition du message peut provenir d'un tampon passé dans la fonction. Il peut provenir d'une ressource de table de messages dans un module déjà chargé. Ou l'appelant peut demander à la fonction de rechercher la ou les ressources de la table de messages du système pour la définition du message. La fonction trouve la définition de message dans une ressource de table de message basée sur un identifiant de message et un identifiant de langue. La fonction copie le texte du message formaté dans un tampon de sortie, traitant toutes les séquences d'insertion intégrées si nécessaire.

La déclaration 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
);

7

Si vous utilisez c #, vous pouvez utiliser ce code:

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

J'ai confirmé que ce code fonctionne. TS ne devrait-il pas accepter cette réponse?
swdev

2
Si c'est nécessaire pour un lancement supplémentaire, il existe un moyen plus simple de le faire en C # avec Win32Exception
SerG

2
@swdev: pourquoi quelqu'un devrait-il accepter une réponse en C # à une question marquée c ou c ++ ? En l'état, cette réponse ne répond même pas à la question posée.
IInspectable

1
Eh bien, je ne me souviens pas avoir voté sur cette proposition de réponse. J'ai corrigé la faille évidente dans la logique de @ swdev. Mais puisque vous n'allez pas me croire, je vais maintenant vous le prouver: Tenez, faites un autre vote défavorable. Celle-ci vient de moi, car cette réponse - même si elle peut être utile compte tenu d'une question différente - n'est tout simplement pas utile étant donné la question qui a été posée.
IInspectable

2
"Je suppose que vous avez des idées utiles à offrir au-delà de l'évidence" - En effet, je l'ai .
IInspectable

4

Si vous avez besoin de prendre en charge MBCS ainsi que Unicode, la réponse de Mr.C64 n'est pas tout à fait suffisante. Le tampon doit être déclaré TCHAR et converti en LPTSTR. Notez que ce code ne traite pas la nouvelle ligne ennuyeuse que Microsoft ajoute au message d'erreur.

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

De plus, par souci de concision, je trouve la méthode suivante utile:

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

Dans le cas où le CStringc'tor lèverait une exception, cette implémentation perd la mémoire allouée par l'appel à FormatMessage.
IInspectable

C'est vrai, mais j'utilise ce code depuis de nombreuses années et cela n'a jamais posé de problème. Le seul cas dans lequel le ctor CString est susceptible de déclencher est l'échec d'allocation de mémoire, et la plupart du code MFC - y compris les éléments fournis par Microsoft - ne gère pas les conditions de manque de mémoire aussi gracieusement que vous le souhaitez. Heureusement, la plupart des PC ont maintenant tellement de mémoire que vous devez travailler assez dur pour tout utiliser. Toute utilisation qui génère une instance CString temporaire (y compris le retour d'un CString) court ce risque. C'est un compromis risque / commodité. De plus, si cela se produit dans un gestionnaire de messages, l'exception sera interceptée.
victimofleisure

La plupart de ce commentaire est faux, désolé. "Cela ne m'est jamais arrivé" est un sacré point faible à souligner, surtout quand on sait comment le code peut échouer. La quantité de mémoire n'a aucun impact sur l'espace d'adressage disponible alloué à un processus. La RAM n'est qu'une optimisation des performances. Copy-elision empêche l'attribution d'un temporaire, lorsque le code est écrit pour autoriser NRVO. Le fait que des exceptions soient interceptées ou non dans un gestionnaire de messages dépend du bitness du processus et des paramètres externes. J'ai soumis une réponse qui montre que la gestion des risques n'entraîne pas d'inconvénients.
IInspectable

En outre, le risque de manquer de mémoire lors de la construction d'un temporaire est sans objet. À ce stade, toutes les ressources ont été libérées, et rien de mauvais n'en sortira.
IInspectable

1
Non, je suis désolé que le code ne vous soit pas utile. Mais TBH est assez bas sur ma liste de problèmes.
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);
}
}

Pourriez-vous également ajouter quelques explications?
Robert

1
Problèmes avec cette implémentation: 1pas de prise en charge Unicode. 2Formatage inapproprié du message d'erreur. Si l'appelant a besoin de traiter la chaîne retournée, il peut simplement le faire. Votre implémentation laisse l'appelant sans option. 3Utilisation d'exceptions mais manque de sécurité d'exception appropriée. Dans le cas où les std::stringopérateurs lèvent des exceptions, le tampon alloué par FormatMessageest divulgué. 4Pourquoi ne pas simplement renvoyer a std::stringau lieu de laisser l'appelant passer un objet par référence?
IInspectable

1

Depuis c ++ 11, vous pouvez utiliser la bibliothèque standard au lieu 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);
    }
}

Il n'y a qu'une petite fenêtre où l'appel GetLastErrorproduit un résultat significatif. Étant donné qu'il s'agit de C ++, la seule option sûre ici consiste à demander à l'appelant de fournir le dernier code d'erreur. Cela n'aide certainement pas que le code présente des appels GetLastError deux fois . De plus, bien que pratique, le manque inhérent de prise en charge des caractères étendus de C ++ ne rend pas l' error_categoryinterface universellement utile. Cela ne fait qu'ajouter à la longue histoire d'opportunités manquées de C ++.
IInspectable

Bon point sur l'appel inutile à GetLastError. Mais je ne vois pas de différence entre appeler GetLastErrorici ou demander à l'appelant de l'appeler. En ce qui concerne C ++ et wchar: n'abandonnez pas l'espoir, Microsoft commence à autoriser les applications à être uniquement UTF-8 .
Chronial

« Je ne vois aucune différence » - Considérez le site appel suivant: log_error("error", GetLastErrorAsString());. Considérez également que log_errorle premier argument de est de type std::string. L'appel de conversion (invisible) c'tor a simplement abandonné vos garanties pour capturer une valeur significative à partir GetLastErrordu point où vous l'appelez.
IInspectable

Pourquoi pensez-vous que le constructeur std :: string appelle une fonction Win32?
Chronial

1
mallocappelle HeapAllocle tas de processus. Le tas de processus est évolutif. Si elle a besoin de se développer, il finira par appeler VirtualAlloc , qui ne défini dernier code d'erreur du thread appelant. Maintenant, cela manque totalement le point, à savoir: C ++ est un champ de mines. Cette implémentation ne fait qu'ajouter à cela, en fournissant une interface avec des garanties implicites qu'elle ne peut tout simplement pas respecter. Si vous pensez qu'il n'y a pas de problème, il devrait vous être facile de prouver l'exactitude de la solution proposée. Bonne chance.
IInspectable

0

Je vais laisser ceci ici car je devrai l'utiliser plus tard. C'est une source pour un petit outil compatible binaire qui fonctionnera aussi bien en assemblage, C et C ++.

GetErrorMessageLib.c (compilé en 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;
    }
}

version en ligne (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 */

cas d'utilisation dynamique (supposé que le code d'erreur est valide, sinon une vérification -1 est nécessaire):

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

cas d'utilisation normal (suppose que le code d'erreur est valide, sinon -1 vérification de retour est nécessaire):

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

exemple d'utilisation avec l'assembly gnu comme dans MinGW32 (encore une fois, supposé que le code d'erreur est valide, sinon -1 vérification est nécessaire).

    .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

résultat: The process cannot access the file because another process has locked a portion of the file.


1
Cela n'ajoute vraiment rien d'utile. En plus de cela, il appelle la version ANSI de FormatMessage, sans raison apparente, et se limite arbitrairement à 80 caractères, encore une fois, sans aucune raison. J'ai peur, ce n'est pas utile.
IInspectable

vous avez raison, j'espérais que personne ne remarquerait l'absence de version Unicode. Je vais vérifier comment définir une chaîne Unicode dans gnu as et modifier ma solution. désolé pour la malhonnêteté.
Dmitry

ok la version unicode est en place. et ce n'est pas une raison arbitraire; tous les messages d'erreur contiennent moins de 80 caractères ou ne valent pas la peine d'être lus, et le code d'erreur est plus important que le message d'erreur. Il n'y a pas de messages d'erreur standard qui dépassent 80 caractères, c'est donc une hypothèse sûre, et quand ce n'est pas le cas, cela ne fuit pas de mémoire.
Dmitry

3
"tous les messages d'erreur comportent moins de 80 caractères ou ne valent pas la peine d'être lus" - C'est faux. Le message d'erreur pour ERROR_LOCK_VIOLATION(33) est: «Le processus ne peut pas accéder au fichier car un autre processus a verrouillé une partie du fichier». C'est à la fois clairement plus de 80 caractères et cela vaut vraiment la peine d'être lu si vous essayez de résoudre un problème et de le trouver dans un fichier journal de diagnostic. Cette réponse n'ajoute aucune valeur substantielle par rapport aux réponses existantes.
IInspectable

Ce n’est pas moins naïf. Il échoue simplement moins souvent. Sauf pour l'implémentation de l'assembly qui concerne la taille de la mémoire tampon (prétendant avoir de la place pour 200 caractères, alors qu'elle n'a de la place que pour 100). Encore une fois, cela n'ajoute rien de substantiel, ce n'est déjà dans aucune des autres réponses. En particulier, c'est pire que cette réponse . Consultez la liste à puces et observez les problèmes que pose votre proposition de mise en œuvre, en plus de ceux que je viens de souligner.
IInspectable
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.