web-dev-qa-db-fra.com

Comment utiliser correctement FormatMessage () en C++?

Sans pour autant:

  • 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);
 }
82
Aaron

Voici le moyen approprié pour obtenir 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 réside dans l'utilisation de l'indicateur FORMAT_MESSAGE_IGNORE_INSERTS. MSDN ne sait pas trop comment utiliser les insertions, mais Raymond Chen note que vous ne devez jamais les utiliser lors de la récupération d'un message système, car vous ne pouvez pas savoir quelles insertions le système attend. 

FWIW, si vous utilisez Visual C++, vous pouvez vous simplifier la vie en utilisant la classe _com_error :

{
   _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 autant que je sache. 

126
Shog9

N'oubliez pas 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
}

Lorsque la classe est créée et détruite sur la pile, errorText pointe vers un emplacement non valide. Dans la plupart des cas, cet emplacement contiendra toujours la chaîne d'erreur, mais cette probabilité disparaît rapidement lors de l'écriture d'applications threadées.

Doncalwaysprocédez comme suit, comme le répond Shog9 ci-dessus:

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

   // do something with the error...

   //automatic cleanup when error goes out of scope
}
14
Marius

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);
}
11
David Hanak

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

}

4
Oleg Zhylin

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

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

Depuis le site MSDN :

Windows 10:
LocalFree n'est pas dans le SDK moderne, il ne peut donc pas être utilisé pour libérer le tampon de résultat. À la place, utilisez HeapFree (GetProcessHeap (), allowedMessage). Dans ce cas, cela revient à appeler LocalFree en mémoire.

Mettre à jour
J'ai constaté que LocalFree est dans 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 l'indicateur FORMAT_MESSAGE_MAX_WIDTH_MASK 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 normaux 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 du message dans le 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 crée-t-il uniquement des messages partiels pour les erreurs système ERROR_SYSTEM_PROCESS_TERMINATED et ERROR_UNHANDLED_EXCEPTION?

4
Class Skeleton

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

#include <system_error>

std::string message = std::system_category().message(hr)
1
Chronial

Le code ci-dessous est le code est l'équivalent C++ que j'ai écrit contrairement à ErrorExit () mais légèrement modifié pour éviter toutes les macros et utiliser le code Unicode L'idée ici est d'éviter les moulages et les mallocs inutiles. Je ne pouvais pas échapper à tous les lancers C mais c'est le meilleur que je puisse rassembler. Relatif à FormatMessageW (), qui nécessite l’affectation d’un pointeur par la fonction de formatage et l’identificateur 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);
}
0
user7533493

Comme indiqué dans d'autres réponses:

  • FormatMessage prend un résultat DWORD et non pas une HRESULT (typiquement GetLastError()).
  • LocalFree est nécessaire pour libérer de la mémoire allouée par FormatMessage

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

  • Enveloppez la FormatMessage dans une classe pour allouer et libérer automatiquement la mémoire si nécessaire
  • Utilisez la surcharge de l'opérateur (par exemple, operator LPTSTR() const { return ...; } afin que votre classe puisse être utilisée comme chaîne
class CFormatMessage
{
public:
    CFormatMessage(DWORD dwError) : m_ErrorText(NULL) { Assign(dwError); }
    ~CFormatMessage() { Clear(); }
    void Clear() { if (m_ErrorText != NULL) { LocalFree(m_ErrorText); m_ErrorText = NULL; } }
    void Assign(DWORD dwError) {
        Clear();
        FormatMessage(
            FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS,
            NULL,
            dwError,
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            (LPTSTR)&m_ErrorText,
            0,
            NULL);
    }
    LPTSTR ErrorText() const { return m_ErrorText; }
    operator LPTSTR() const { return ErrorText(); }
protected:
    LPTSTR m_ErrorText;
};

Vous trouverez 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";
0
Stephen Quan