2009-01-18 6 views

Odpowiedz

123

Oto właściwy sposób, aby dostać się komunikat o błędzie z powrotem z układu na HRESULT (o nazwie HRESULT w tym przypadku, czy można zastąpić go 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 tą a Dawidem Hanakiem jest użycie flagi FORMAT_MESSAGE_IGNORE_INSERTS. MSDN jest nieco niejasne, w jaki sposób należy używać wstawek, ale Raymond Chen notes that you should never use them podczas pobierania wiadomości systemowej, ponieważ nie masz możliwości sprawdzenia, które wstawienia system oczekuje.

FWIW, jeśli używasz Visual C++ można uczynić swoje życie trochę łatwiejsze za pomocą klasy _com_error:

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

    // do something with the error... 

    //automatic cleanup when error goes out of scope 
} 

Nie jest częścią MFC lub ATL bezpośrednio o ile jestem świadomy.

+4

Uwaga: ten kod używa hResult zamiast kodu błędu Win32: to są różne rzeczy! Możesz otrzymać tekst o zupełnie innym błędzie niż ten, który faktycznie wystąpił. –

+0

Doskonały punkt, @Andrei - i nawet jeśli błąd * jest * błędem Win32, ta procedura zakończy się sukcesem, jeśli jest to błąd * systemu * - solidny mechanizm obsługi błędów musiałby być świadomy źródła błąd, sprawdź kod przed wywołaniem FormatMessage i zamiast tego zapytaj o inne źródła. – Shog9

+0

@AndreiBelogortseff Skąd mogę wiedzieć, co używać w każdym przypadku? Na przykład 'RegCreateKeyEx' zwraca wartość' LONG'. Jego dokumenty mówią, że mogę użyć 'FormatMessage', aby pobrać błąd, ale muszę rzucić' LONG' w 'HRESULT'. – csl

9

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); 
} 
+0

void HandleLastError (hresult)? – Aaron

+0

Cześć David, wolałbym nie używać MFC: "TRACE()" ... – Aaron

+1

Z pewnością możesz dokonać tych adaptacji samodzielnie. – oefe

13

Należy pamiętać, że nie można wykonać następujące czynności:

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

    // do something with the error... 

    //automatic cleanup when error goes out of scope 
} 

Jak jest tworzony i zniszczone na stosie pozostawiając errorText zwrócić się do nieprawidłowej lokalizacji klasa. W większości przypadków ta lokalizacja nadal będzie zawierała ciąg błędu, ale prawdopodobieństwo to spada szybko podczas pisania aplikacji z gwintami.

Więc zawsze zrobić to w następujący sposób, jak odpowiadał Shog9 powyżej:

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

    // do something with the error... 

    //automatic cleanup when error goes out of scope 
} 
+6

Obiekt '_com_error' jest tworzony na stosie w * obu * twoich przykładach . Termin, którego szukasz, jest * tymczasowy *. W poprzednim przykładzie obiekt jest tymczasowy, który jest niszczony na końcu instrukcji. –

+0

Tak, oznaczało to. Ale mam nadzieję, że większość ludzi byłaby w stanie to zrozumieć z kodu. Technicznie tymczasowe nie są niszczone na końcu instrukcji, ale na końcu punktu sekwencji. (co jest tym samym w tym przykładzie, więc to jest tylko dzielenie włosów.) – Marius

+5

btw, _com_error jest zadeklarowane w 'comdef.h' – Francois

4

Oto wersja funkcji Dawida, który obsługuje standard 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

ten jest raczej dodatkiem do większości odpowiedzi, ale zamiast używać LocalFree(errorText) użyć HeapFree funkcję:

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

From the MSDN site:

systemie Windows 10:
LocalFree nie jest w nowoczesnej SDK, więc nie może być używany, aby zwolnić bufor wynik. Zamiast tego użyj HeapFree (GetProcessHeap(), allocMessage). W tym przypadku jest to to samo, co wywołanie LocalFree w pamięci.

Aktualizacja
stwierdziliśmy, że LocalFree jest w wersji 10.0.10240.0 SDK (linia 1108 w WinBase.h). Mimo to ostrzeżenie nadal występuje w powyższym linku.

#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 

Aktualizacja 2
Chciałbym również zasugerować z flagą FORMAT_MESSAGE_MAX_WIDTH_MASK posprzątać podziały wiersza w komunikatach systemowych.

From the MSDN site:

FORMAT_MESSAGE_MAX_WIDTH_MASK
Funkcja ignoruje regularne podziały wiersza w tekście definicji wiadomość. Funkcja przechowuje zakodowane na sztywno podziały linii w tekście definicji komunikatu w buforze wyjściowym. Funkcja nie generuje żadnych nowych podziałów linii.

Update 3
Nie wydaje się być 2 poszczególne kody błędów systemu, które nie zwracają pełną wiadomość używając zalecanego podejścia:

Why does FormatMessage only create partial messages for ERROR_SYSTEM_PROCESS_TERMINATED and ERROR_UNHANDLED_EXCEPTION system errors?

0

Poniższy kod jest kodem to C++ odpowiednik napisałem w przeciwieństwie do Microsoft's ErrorExit(), ale nieznacznie zmieniono, aby ominąć wszystkie makra i używać unikodu. Chodzi o to, aby uniknąć niepotrzebnych obsad i malloców. Nie mogłem uciec od wszystkich rzutów C, ale to najlepsze, co mogłem zebrać. Pertaining to FormatMessageW(), który wymaga, aby wskaźnik był przydzielany przez funkcję formatu i identyfikator błędu z GetLastError(). Wskaźnik po static_cast może być użyty jak normalny 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); 
}