Comment utiliser FormatMessage () correctement en C ++?

89

Sans :

  • MFC
  • ATL

Comment puis-je utiliser FormatMessage()pour obtenir le texte d'erreur pour un HRESULT?

 HRESULT hresult = application.CreateInstance("Excel.Application");

 if (FAILED(hresult))
 {
     // what should i put here to obtain a human-readable
     // description of the error?
     exit (hresult);
 }
Aaron
la source

Réponses:

133

Voici la bonne façon de récupérer un message d'erreur du système pour un HRESULT(nommé hresult dans ce cas, ou vous pouvez le remplacer par GetLastError()):

LPTSTR errorText = NULL;

FormatMessage(
   // use system message tables to retrieve error text
   FORMAT_MESSAGE_FROM_SYSTEM
   // allocate buffer on local heap for error text
   |FORMAT_MESSAGE_ALLOCATE_BUFFER
   // Important! will fail otherwise, since we're not 
   // (and CANNOT) pass insertion parameters
   |FORMAT_MESSAGE_IGNORE_INSERTS,  
   NULL,    // unused with FORMAT_MESSAGE_FROM_SYSTEM
   hresult,
   MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
   (LPTSTR)&errorText,  // output 
   0, // minimum size for output buffer
   NULL);   // arguments - see note 
   
if ( NULL != errorText )
{
   // ... do something with the string `errorText` - log it, display it to the user, etc.

   // release memory allocated by FormatMessage()
   LocalFree(errorText);
   errorText = NULL;
}

La principale différence entre cela et la réponse de David Hanak est l'utilisation du FORMAT_MESSAGE_IGNORE_INSERTSdrapeau. MSDN est un peu incertain sur la façon dont les insertions doivent être utilisées, mais Raymond Chen note que vous ne devriez jamais les utiliser lors de la récupération d'un message système, car vous n'avez aucun moyen de savoir à quelles insertions le système attend.

FWIW, si vous utilisez Visual C ++, vous pouvez vous simplifier la vie un peu en utilisant la _com_errorclasse:

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

   //automatic cleanup when error goes out of scope
}

Ne fait pas partie de MFC ou ATL directement pour autant que je sache.

Shog9
la source
8
Attention: ce code utilise hResult à la place d'un code d'erreur Win32: ce sont des choses différentes! Vous pouvez obtenir le texte d'une erreur complètement différente de celle qui s'est réellement produite.
Andrei Belogortseff
1
Excellent point, @Andrei - et en effet, même si l'erreur est une erreur Win32, cette routine ne réussira que s'il s'agit d'une erreur système - un mécanisme robuste de gestion des erreurs aurait besoin de connaître la source de l'erreur, d'examiner le code avant d'appeler FormatMessage et peut-être interroger d'autres sources à la place.
Shog9
1
@AndreiBelogortseff Comment savoir quoi utiliser dans chaque cas? Par exemple, RegCreateKeyExrenvoie un LONG. Sa documentation indique que je peux utiliser FormatMessagepour récupérer l'erreur, mais je dois LONGconvertir le fichier dans un fichier HRESULT.
csl
FormatMessage () prend un DWORD, @csl, un entier non signé qui est supposé être un code d'erreur valide. Toutes les valeurs de retour - ou HRESULTS d'ailleurs - ne seront pas des codes d'erreur valides; le système suppose que vous avez vérifié que c'est avant d'appeler la fonction. Les documents pour RegCreateKeyEx doivent spécifier quand la valeur de retour peut être interprété comme une erreur ... Effectuez cette vérification d' abord , et alors seulement appeler FormatMessage.
Shog9
1
MSDN fournit maintenant leur version d'un peu le même code.
ahmd0
14

Gardez à l'esprit que vous ne pouvez pas effectuer les opérations suivantes:

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

   // do something with the error...

   //automatic cleanup when error goes out of scope
}

Au fur et à mesure que la classe est créée et détruite sur la pile, le message errorText pointe vers un emplacement non valide. Dans la plupart des cas, cet emplacement contiendra toujours la chaîne d'erreur, mais cette probabilité diminue rapidement lors de l'écriture d'applications threadées.

Alors faites-le toujours comme suit, comme répondu par Shog9 ci-dessus:

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

   // do something with the error...

   //automatic cleanup when error goes out of scope
}
Marius
la source
7
L' _com_errorobjet est créé sur la pile dans vos deux exemples. Le terme que vous recherchez est temporaire . Dans le premier exemple, l'objet est un temporaire qui est détruit à la fin de l'instruction.
Rob Kennedy
Ouais, ça voulait dire ça. Mais j'espère que la plupart des gens seront au moins capables de comprendre cela à partir du code. Techniquement, les temporaires ne sont pas détruits à la fin de l'instruction, mais à la fin du point de séquence. (ce qui est la même chose dans cet exemple, il ne s'agit donc que de couper les cheveux.)
Marius
1
Si vous voulez le rendre sûr (peut-être pas très efficace ), vous pouvez le faire en C ++:std::wstring strErrorText = _com_error(hresult).ErrorMessage();
ahmd0
12

Essaye ça:

void PrintLastError (const char *msg /* = "Error occurred" */) {
        DWORD errCode = GetLastError();
        char *err;
        if (!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
                           NULL,
                           errCode,
                           MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // default language
                           (LPTSTR) &err,
                           0,
                           NULL))
            return;

        static char buffer[1024];
        _snprintf(buffer, sizeof(buffer), "ERROR: %s: %s\n", msg, err);
        OutputDebugString(buffer); // or otherwise log it
        LocalFree(err);
}
David Hanak
la source
void HandleLastError (hresult)?
Aaron
1
Vous pouvez sûrement faire ces adaptations vous-même.
oefe
@Atklin: Si vous voulez utiliser hresult à partir d'un paramètre, vous n'avez évidemment pas besoin de la première ligne (GetLastError ()).
David Hanak
4
GetLastError ne renvoie pas de HResult. Il renvoie un code d'erreur Win32. Peut-être préférer le nom PrintLastError car cela ne gère rien. Et assurez-vous d'utiliser FORMAT_MESSAGE_IGNORE_INSERTS.
Rob Kennedy
Merci pour votre aide les gars :) - très apprécié
Aaron
5

C'est plus un ajout à la majorité des réponses, mais au lieu d'utiliser, LocalFree(errorText)utilisez la HeapFreefonction:

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

Depuis le site MSDN :

Windows 10 :
LocalFree ne fait pas partie du SDK moderne, il ne peut donc pas être utilisé pour libérer le tampon de résultat. Au lieu de cela, utilisez HeapFree (GetProcessHeap (), sharedMessage). Dans ce cas, cela revient à appeler LocalFree en mémoire.

Mise à jour que
j'ai trouvée LocalFreedans la version 10.0.10240.0 du SDK (ligne 1108 dans WinBase.h). Cependant, l'avertissement existe toujours dans le lien ci-dessus.

#pragma region Desktop Family or OneCore Family
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM)

WINBASEAPI
_Success_(return==0)
_Ret_maybenull_
HLOCAL
WINAPI
LocalFree(
    _Frees_ptr_opt_ HLOCAL hMem
    );

#endif /* WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM) */
#pragma endregion

Mise à jour 2
Je suggérerais également d'utiliser le FORMAT_MESSAGE_MAX_WIDTH_MASKdrapeau pour ranger les sauts de ligne dans les messages système.

Depuis le site MSDN :

FORMAT_MESSAGE_MAX_WIDTH_MASK
La fonction ignore les sauts de ligne réguliers dans le texte de définition du message. La fonction stocke les sauts de ligne codés en dur dans le texte de définition de message dans la mémoire tampon de sortie. La fonction ne génère aucun nouveau saut de ligne.

Mise à jour 3
Il semble y avoir 2 codes d'erreur système particuliers qui ne renvoient pas le message complet en utilisant l'approche recommandée:

Pourquoi FormatMessage ne crée-t-il que des messages partiels pour les erreurs système ERROR_SYSTEM_PROCESS_TERMINATED et ERROR_UNHANDLED_EXCEPTION?

Squelette de classe
la source
4

Voici une version de la fonction de David qui gère Unicode

void HandleLastError(const TCHAR *msg /* = "Error occured" */) {
    DWORD errCode = GetLastError();
    TCHAR *err;
    if (!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
                       NULL,
                       errCode,
                       MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // default language
                       (LPTSTR) &err,
                       0,
                       NULL))
        return;

    //TRACE("ERROR: %s: %s", msg, err);
    TCHAR buffer[1024];
    _sntprintf_s(buffer, sizeof(buffer), _T("ERROR: %s: %s\n"), msg, err);
    OutputDebugString(buffer);
    LocalFree(err);

}

Oleg Zhylin
la source
1
Notez que vous ne transmettez pas la taille de tampon correcte _sntprintf_sdans le cas UNICODE. La fonction prend le nombre de caractères, donc vous voulez _countofou ARRAYSIZEaka à la sizeof(buffer) / sizeof(buffer[0])place du sizeof.
ThFabba
3

Depuis c ++ 11, vous pouvez utiliser la bibliothèque standard au lieu de FormatMessage:

#include <system_error>

std::string message = std::system_category().message(hr)
Chroniques
la source
2

Comme indiqué dans d'autres réponses:

  • FormatMessageprend un DWORDrésultat pas un HRESULT(généralement GetLastError()).
  • LocalFree est nécessaire pour libérer la mémoire allouée par FormatMessage

J'ai pris les points ci-dessus et en ai ajouté quelques autres pour ma réponse:

  • Enveloppez le FormatMessagedans une classe pour allouer et libérer de la mémoire si nécessaire
  • Utilisez la surcharge d'opérateur (par exemple operator LPTSTR() const { return ...; }pour que votre classe puisse être utilisée comme une chaîne
class CFormatMessage
{
public:
    CFormatMessage(DWORD dwMessageId,
                   DWORD dwLanguageId = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT)) :
        m_text(NULL)
    {
        Assign(dwMessageId, dwLanguageId);
    }

    ~CFormatMessage()
    {
        Clear();
    }

    void Clear()
    {
        if (m_text)
        {
            LocalFree(m_text);
            m_text = NULL;
        }
    }

    void Assign(DWORD dwMessageId,
                DWORD dwLanguageId = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT))
    {
        Clear();
        DWORD dwFlags = FORMAT_MESSAGE_FROM_SYSTEM
            | FORMAT_MESSAGE_ALLOCATE_BUFFER
            | FORMAT_MESSAGE_IGNORE_INSERTS,
        FormatMessage(
            dwFlags,
            NULL,
            dwMessageId,
            dwLanguageId,
            (LPTSTR) &m_text,
            0,
            NULL);
    }

    LPTSTR text() const { return m_text; }
    operator LPTSTR() const { return text(); }

protected:
    LPTSTR m_text;

};

Trouvez une version plus complète du code ci-dessus ici: https://github.com/stephenquan/FormatMessage

Avec la classe ci-dessus, l'utilisation est simplement:

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

Le code ci-dessous est le code est l'équivalent C ++ que j'ai écrit contrairement à ErrorExit () de Microsoft mais légèrement modifié pour éviter toutes les macros et utiliser unicode. L'idée ici est d'éviter les moulages et les mallocs inutiles. Je n'ai pas pu échapper à tous les lancers C mais c'est le meilleur que je puisse rassembler. Concernant FormatMessageW (), qui nécessite qu'un pointeur soit alloué par la fonction de format et l'ID d'erreur de GetLastError (). Le pointeur après static_cast peut être utilisé comme un pointeur wchar_t normal.

#include <string>
#include <windows.h>

void __declspec(noreturn) error_exit(const std::wstring FunctionName)
{
    // Retrieve the system error message for the last-error code
    const DWORD ERROR_ID = GetLastError();
    void* MsgBuffer = nullptr;
    LCID lcid;
    GetLocaleInfoEx(L"en-US", LOCALE_RETURN_NUMBER | LOCALE_ILANGUAGE, (wchar_t*)&lcid, sizeof(lcid));

    //get error message and attach it to Msgbuffer
    FormatMessageW(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL, ERROR_ID, lcid, (wchar_t*)&MsgBuffer, 0, NULL);
    //concatonate string to DisplayBuffer
    const std::wstring DisplayBuffer = FunctionName + L" failed with error " + std::to_wstring(ERROR_ID) + L": " + static_cast<wchar_t*>(MsgBuffer);

    // Display the error message and exit the process
    MessageBoxExW(NULL, DisplayBuffer.c_str(), L"Error", MB_ICONERROR | MB_OK, static_cast<WORD>(lcid));

    ExitProcess(ERROR_ID);
}

la source