Jak prawidłowo używać FormatMessage () w C ++?

90

Bez :

  • MFC
  • ATL

Jak mogę użyć, FormatMessage()aby uzyskać tekst błędu dla 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
źródło

Odpowiedzi:

134

Oto właściwy sposób na odzyskanie komunikatu o błędzie z systemu HRESULT(w tym przypadku o nazwie hresult lub możesz go zastąpić 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;
}

Kluczową różnicą między tym a odpowiedzią Davida Hanaka jest użycie FORMAT_MESSAGE_IGNORE_INSERTSflagi. MSDN nie jest do końca jasne, w jaki sposób powinny być używane wstawki, ale Raymond Chen zauważa, że ​​nigdy nie należy ich używać podczas pobierania wiadomości systemowej, ponieważ nie można dowiedzieć się, jakich wstawek oczekuje system.

FWIW, jeśli używasz Visual C ++, możesz trochę ułatwić sobie życie, używając _com_errorklasy:

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

   //automatic cleanup when error goes out of scope
}

O ile wiem, nie jest częścią MFC ani ATL bezpośrednio.

Shog9
źródło
8
Uwaga: ten kod używa hResult zamiast kodu błędu Win32: to są różne rzeczy! Możesz otrzymać tekst zupełnie innego błędu niż ten, który faktycznie wystąpił.
Andrei Belogortseff
1
Doskonała uwaga, @Andrei - i rzeczywiście, nawet jeśli błąd jest błędem Win32, ta procedura powiedzie się tylko wtedy, gdy jest to błąd systemowy - solidny mechanizm obsługi błędów musiałby być świadomy źródła błędu, zbadać kod przed wywołaniem FormatMessage i być może zamiast tego zapytać inne źródła.
Shog9
1
@AndreiBelogortseff Skąd mam wiedzieć, czego użyć w każdym przypadku? Na przykład RegCreateKeyExzwraca a LONG. Jego dokumentacja mówi, że mogę użyć FormatMessagedo odzyskania błędu, ale muszę rzucić LONGna plik HRESULT.
csl
FormatMessage () przyjmuje DWORD @csl, liczbę całkowitą bez znaku, który jest zakładany być poprawny kod błędu. Nie wszystkie zwracane wartości - lub HRESULTS w tym przypadku - będą prawidłowymi kodami błędów; system zakłada, że ​​zweryfikowałeś, że nastąpiło to przed wywołaniem funkcji. Dokumentacja RegCreateKeyEx powinna określać, kiedy zwracana wartość może być interpretowana jako błąd ... Najpierw wykonaj to sprawdzenie , a dopiero potem wywołaj FormatMessage.
Shog9
1
W rzeczywistości MSDN udostępnia teraz swoją wersję tego samego kodu.
ahmd0
14

Pamiętaj, że nie możesz wykonać następujących czynności:

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

   // do something with the error...

   //automatic cleanup when error goes out of scope
}

Gdy klasa jest tworzona i niszczona na stosie, pozostawiając errorText wskazujący nieprawidłową lokalizację. W większości przypadków ta lokalizacja nadal będzie zawierać ciąg błędu, ale prawdopodobieństwo to szybko spada podczas pisania aplikacji z wątkami.

Więc zawsze rób to w następujący sposób, zgodnie z odpowiedzią Shog9 powyżej:

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

   // do something with the error...

   //automatic cleanup when error goes out of scope
}
Marius
źródło
7
_com_errorObiekt jest tworzony na stosie w obu swoich przykładach. Termin, którego szukasz, jest tymczasowy . W poprzednim przykładzie obiekt jest tymczasowy, który jest niszczony na końcu instrukcji.
Rob Kennedy,
Tak, to znaczyło. Ale mam nadzieję, że większość ludzi przynajmniej będzie w stanie to rozgryźć na podstawie kodu. Technicznie czasowe nie są niszczone na końcu instrukcji, ale na końcu punktu sekwencji. (co jest tym samym w tym przykładzie, więc to tylko dzielenie włosów).
Marius
1
Jeśli chcesz, aby było bezpieczne (może niezbyt wydajne ), możesz to zrobić w C ++:std::wstring strErrorText = _com_error(hresult).ErrorMessage();
ahmd0
11

Spróbuj tego:

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
źródło
void HandleLastError (hresult)?
Aaron
1
Z pewnością możesz sam dokonać tych adaptacji.
oefe
@Atklin: Jeśli chcesz użyć hresult z parametru, oczywiście nie potrzebujesz pierwszej linii (GetLastError ()).
David Hanak
4
GetLastError nie zwraca HResult. Zwraca kod błędu Win32. Może wolałbym nazwę PrintLastError, ponieważ tak naprawdę nic nie obsługuje . I pamiętaj, aby użyć FORMAT_MESSAGE_IGNORE_INSERTS.
Rob Kennedy
Dzięki za pomoc :) - bardzo doceniane
Aaron
5

Jest to bardziej dodatek do większości odpowiedzi, ale zamiast LocalFree(errorText)używać HeapFreefunkcji:

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

Z witryny MSDN :

Windows 10 :
LocalFree nie znajduje się w nowoczesnym SDK, więc nie można go użyć do zwolnienia buforu wyników. Zamiast tego użyj HeapFree (GetProcessHeap (), assignMessage). W tym przypadku jest to to samo, co wywołanie LocalFree w pamięci.

Aktualizacja
znalazłem LocalFreew wersji 10.0.10240.0 SDK (wiersz 1108 w WinBase.h). Jednak w powyższym linku nadal istnieje ostrzeżenie.

#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

Zaktualizuj 2
Sugerowałbym również użycie FORMAT_MESSAGE_MAX_WIDTH_MASKflagi do uporządkowania podziałów wierszy w komunikatach systemowych.

Z witryny MSDN :

FORMAT_MESSAGE_MAX_WIDTH_MASK
Funkcja ignoruje zwykłe podziały wierszy w tekście definicji wiadomości. Funkcja przechowuje zakodowane na stałe podziały wierszy w tekście definicji komunikatu w buforze wyjściowym. Funkcja nie generuje nowych podziałów wierszy.

Zaktualizuj 3
Wydaje się, że istnieją 2 konkretne kody błędów systemu, które nie zwracają pełnego komunikatu przy zastosowaniu zalecanego podejścia:

Dlaczego FormatMessage tworzy tylko częściowe komunikaty dla błędów systemowych ERROR_SYSTEM_PROCESS_TERMINATED i ERROR_UNHANDLED_EXCEPTION?

Szkielet klasy
źródło
4

Oto wersja funkcji Davida, która obsługuje 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
źródło
1
Zwróć uwagę, że nie przekazujesz prawidłowego rozmiaru buforu do _sntprintf_sw przypadku UNICODE. Funkcja przyjmuje liczbę znaków, więc chcesz _countoflub ARRAYSIZEaka sizeof(buffer) / sizeof(buffer[0])zamiast sizeof.
ThFabba
4

Od C ++ 11 możesz użyć standardowej biblioteki zamiast FormatMessage:

#include <system_error>

std::string message = std::system_category().message(hr)
Chronial
źródło
2

Jak wskazano w innych odpowiedziach:

  • FormatMessageprzyjmuje DWORDwynik, a nie HRESULT(zazwyczaj GetLastError()).
  • LocalFree jest potrzebny do zwolnienia pamięci przydzielonej przez FormatMessage

Wziąłem powyższe punkty i dodałem kilka więcej do mojej odpowiedzi:

  • Zawiń FormatMessagew klasie, aby przydzielić i zwolnić pamięć w razie potrzeby
  • Użyj przeciążenia operatora (np. Aby operator LPTSTR() const { return ...; }Twoja klasa mogła być używana jako łańcuch
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;

};

Znajdź pełniejszą wersję powyższego kodu tutaj: https://github.com/stephenquan/FormatMessage

W przypadku powyższej klasy użycie jest po prostu:

    std::wcout << (LPTSTR) CFormatMessage(GetLastError()) << L"\n";
Stephen Quan
źródło
0

Poniższy kod jest odpowiednikiem C ++, który napisałem w przeciwieństwie do Microsoft ErrorExit (), ale nieznacznie zmieniony, aby uniknąć wszystkich makr i używać Unicode. Chodzi o to, aby uniknąć niepotrzebnych odlewów i młotków. Nie mogłem uciec od wszystkich rzutów C, ale to najlepsze, na co mogłem się zdobyć. Odnosi się do FormatMessageW (), który wymaga, aby wskaźnik został przydzielony przez funkcję format i identyfikator błędu z GetLastError (). Wskaźnik po static_cast może być używany jak zwykły wskaźnik wchar_t.

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

źródło