kiedyś w sekcji narzędzi w programie Visual Studio znajdowało się wyszukiwanie błędów w plikach exe, które robiło to całkiem dobrze, gdy do debugowania potrzebny jest tylko komunikat o błędzie.
ColdCat,
1
@ColdCat: W przypadku debugowania o wiele łatwiej jest po prostu dodać @err,hrzegarek, a debugger automatycznie konwertuje ostatni kod błędu na reprezentację czytelną dla człowieka. Specyfikator ,hrformatu działa dla każdego wyrażenia, którego wynikiem jest wartość całkowita, np. 5,hrZegarek wyświetli „ERROR_ACCESS_DENIED: Odmowa dostępu”. .
//Returns the last Win32 error, in string format. Returns an empty string if there is no error.std::stringGetLastErrorAsString(){
//Get the error message, if any.
DWORD errorMessageID = ::GetLastError();
if(errorMessageID == 0)
returnstd::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::stringmessage(messageBuffer, size);
//Free the buffer.
LocalFree(messageBuffer);
return message;
}
Uważam, że (LPSTR)&messageBufferw tym przypadku musisz przejść , ponieważ w przeciwnym razie FormatMessageA nie może zmienić swojej wartości, aby wskazywała na przydzielony bufor.
Kylotan
2
Och, wow, tak, to trochę dziwne. Jak zmodyfikowałby wskaźnik? Ale przekazanie mu adresu wskaźnika (wskaźnik do wskaźnika), ale rzutowanie go na zwykły wskaźnik ... Dziwność Win32. Dzięki za ostrzeżenia, naprawiłem to we własnej bazie kodu (i mojej odpowiedzi). Bardzo subtelny chwyt.
Jamin Gray,
1
Dziękuję bardzo, Twój przykład jest znacznie wyraźniejszy niż ten z MSDN. Co więcej, ten z MSDN nawet nie mógł się skompilować. Zawiera strsafe.hnagłówek, który wcale nie jest bezpieczny , powodując kilka błędów kompilatora w winuser.hi winbase.h.
Hi-Angel,
2
Niektóre identyfikatory błędów nie są obsługiwane. Na przykład 0x2EE7, ERROR_INTERNET_NAME_NOT_RESOLVED powoduje nowy błąd podczas wywoływania FormatMessage: 0x13D, system nie może znaleźć tekstu wiadomości dla numeru wiadomości 0x% 1 w pliku wiadomości dla% 2.
Brent,
3
Problemy z tą implementacją: 1GetLastErrorjest potencjalnie wywoływany za późno. 2Brak obsługi Unicode. 3Stosowanie wyjątków bez wdrażania wyjątkowych gwarancji bezpieczeństwa.
Niespodziewane
66
Zaktualizowano (11/2017) w celu uwzględnienia niektórych uwag.
@ Hi-Angel - przykład zakłada, że kompilujesz ze zdefiniowanym kodem UNICODE. „FormatMessage” jest w rzeczywistości makrem, które rozwija się do „FormatMessageA” dla buforów znaków Ansi / MBCS lub „FormatMessageW” dla buforów UTF16 / UNICODE, w zależności od sposobu kompilacji aplikacji. Pozwoliłem sobie edytować powyższy przykład, aby jawnie wywołać wersję, która pasuje do typu bufora wyjściowego (wchar_t).
Problemy z tą implementacją: 1brak określenia ważnej FORMAT_MESSAGE_IGNORE_INSERTSflagi. 2GetLastErrorpotencjalnie wezwany za późno. 3Arbitralne ograniczenie wiadomości do 256 jednostek kodu. 4Brak obsługi błędów.
Niespodziewane
1
sizeof (buf) powinno być ARRAYSIZE (buf), ponieważ FormatMessage oczekuje rozmiaru bufora w TCHAR, a nie w bajtach.
Kai
1
Nie możemy użyć 256zamiast (sizeof(buf) / sizeof(wchar_t)? czy to jest akceptowalne i bezpieczne?
GetLastError zwraca numeryczny kod błędu. Aby uzyskać opisowy komunikat o błędzie (np. Aby wyświetlić go użytkownikowi), możesz wywołać 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)
{
returnFALSE;
}
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);
}
W C ++ możesz znacznie uprościć interfejs, używając klasy 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);
returnString(ptrBuffer.get(), cchMsg);
}
else
{
auto error_code{ ::GetLastError() };
throw std::system_error( error_code, std::system_category(),
"Failed to retrieve error message string.");
}
}
UWAGA: Te funkcje działają również dla wartości HRESULT. Po prostu zmień pierwszy parametr z DWORD dwErrorCode na HRESULT hResult. Reszta kodu może pozostać niezmieniona.
Te implementacje zapewniają następujące ulepszenia w stosunku do istniejących odpowiedzi:
Kompletny przykładowy kod, a nie tylko odniesienie do interfejsu API do wywołania.
Zapewnia implementacje C i C ++.
Działa zarówno dla ustawień projektu Unicode, jak i MBCS.
Pobiera kod błędu jako parametr wejściowy. Jest to ważne, ponieważ ostatni kod błędu wątku jest ważny tylko w dobrze zdefiniowanych punktach. Parametr wejściowy umożliwia dzwoniącemu przestrzeganie udokumentowanej umowy.
Implementuje odpowiednie bezpieczeństwo wyjątków. W przeciwieństwie do wszystkich innych rozwiązań, które niejawnie używają wyjątków, ta implementacja nie spowoduje wycieku pamięci w przypadku zgłoszenia wyjątku podczas konstruowania wartości zwracanej.
Właściwa obsługa błędów / raportowanie błędów, w przeciwieństwie do niektórych innych odpowiedzi, po cichu ignoruje błędy.
Ta odpowiedź została uwzględniona w dokumentacji przepełnienia stosu. Następujący użytkownicy przyczynili się do powstania przykładu: stackptr , Ajay , Cody Gray ♦ , IInspectable .
Zamiast rzucać std::runtime_error, proponuję rzucić, std::system_error(lastError, std::system_category(), "Failed to retrieve error message string.")gdzie lastErrorbyłaby wartość zwracana GetLastError()po nieudanym FormatMessage()wywołaniu.
zett42
Jaka jest zaleta korzystania z wersji C nad FormatMessagebezpośrednio?
Micha Wiedenmann
@mic: To jak pytanie: „Jaka jest zaleta funkcji w C?” Funkcje zmniejszają złożoność, zapewniając abstrakcje. W tym przypadku abstrakcja zmniejsza liczbę parametrów z 7 do 3, implementuje udokumentowaną umowę API poprzez przekazywanie zgodnych flag i parametrów oraz koduje udokumentowaną logikę raportowania błędów. Zapewnia zwięzły interfejs z łatwą do odgadnięcia semantyką, dzięki czemu nie musisz poświęcać 11 minut na przeczytanie dokumentacji .
Niespodziewane
Podoba mi się ta odpowiedź, jest bardzo jasne, że zwrócono szczególną uwagę na poprawność kodu. Mam dwa pytania. 1) Dlaczego używasz ::HeapFree(::GetProcessHeap(), 0, p)w usuwaniu zamiast tego, ::LocalFree(p)co sugeruje dokumentacja? 2) Zdaję sobie sprawę, że dokumentacja mówi, żeby to zrobić w ten sposób, ale nie reinterpret_cast<LPTSTR>(&psz)narusza ścisłej zasady aliasingu?
Teh JoE
@teh: Dziękuję za opinię. Pytanie 1): Szczerze mówiąc, nie wiem, czy to jest bezpieczne. Nie pamiętam, do kogo lub dlaczego wezwanie HeapFreezostało wprowadzone, podczas gdy był to nadal temat w Dokumentacji SO. Będę musiał zbadać (chociaż dokumentacja wydaje się być jasna, że nie jest to bezpieczne). Pytanie 2): To jest dwojakie. W przypadku konfiguracji MBCS LPTSTRjest aliasem dla char*. Ta obsada jest zawsze bezpieczna. W przypadku konfiguracji Unicode, rzutowanie do wchar_t*jest w każdym razie podejrzane. Nie wiem, czy jest to bezpieczne w C, ale najprawdopodobniej nie jest w C ++. Też musiałbym to zbadać.
Formatuje ciąg wiadomości. Funkcja wymaga definicji wiadomości jako danych wejściowych. Definicja wiadomości może pochodzić z bufora przekazanego do funkcji. Może pochodzić z zasobu tabeli komunikatów w już załadowanym module. Lub dzwoniący może poprosić funkcję o przeszukanie zasobów tabeli komunikatów systemu w celu znalezienia definicji komunikatu. Funkcja znajduje definicję komunikatu w zasobie tabeli komunikatów na podstawie identyfikatora komunikatu i identyfikatora języka. Funkcja kopiuje sformatowany tekst komunikatu do bufora wyjściowego, przetwarzając na żądanie wszelkie osadzone sekwencje wstawiania.
Potwierdziłem, że ten kod działa. Czy TS nie powinien zaakceptować tej odpowiedzi?
swdev
2
Jeśli jest to konieczne do dalszego rzucania, jest prostszy sposób na zrobienie tego w C # z Win32Exception
SerG
2
@swdev: Dlaczego ktoś miałby akceptować odpowiedź w języku C # na pytanie oznaczone tagiem c lub c ++ ? W obecnym brzmieniu ta odpowiedź nie odnosi się nawet do zadawanego pytania.
Niespodziewane
1
Cóż, nie przypominam sobie, żebym głosował nad tą proponowaną odpowiedzią. Rozwiązałem oczywistą wadę w logice @ swdev. Ale skoro mi nie uwierzysz, teraz ci to udowodnię: Masz kolejny głos przeciw. To jest ode mnie, ponieważ ta odpowiedź - chociaż może być przydatna przy innym pytaniu - po prostu nie jest przydatna, biorąc pod uwagę pytanie, które było zadawane.
Niespodziewane
2
„Sądzę, że masz pomocne spostrzeżenia, które wykraczają poza oczywiste” - Rzeczywiście, mam .
Niespodziewane
5
Od C ++ 11 możesz użyć standardowej biblioteki zamiast FormatMessage:
#include<system_error>std::stringGetLastErrorAsString(){
DWORD errorMessageID = ::GetLastError();
if (errorMessageID == 0) {
returnstd::string(); //No error message has been recorded
} else {
returnstd::system_category().message(errorMessageID);
}
}
Jest tylko małe okienko, w którym wywołanie GetLastErrordaje znaczący wynik. Ponieważ jest to C ++, jedyną bezpieczną opcją jest podanie przez dzwoniącego ostatniego kodu błędu. Z pewnością nie pomaga, że przedstawiony kod wywołuje GetLastErrordwa razy . Ponadto, choć wygodny, nieodłączny brak obsługi szerokich znaków w C ++ nie czyni error_categoryinterfejsu uniwersalnym. To tylko dodaje do długiej historii straconych szans w C ++.
Niespodziewane
Dobra uwaga na temat bezużytecznego połączenia do GetLastError. Ale nie widzę różnicy między dzwonieniem GetLastErrortutaj a dzwonieniem przez dzwoniącego. Odnośnie C ++ i wchar: Nie trać nadziei, Microsoft zaczyna zezwalać aplikacjom tylko na UTF-8 .
Chronial
„Widzę żadnej różnicy” - Rozważmy następującą witrynę rozmowę: log_error("error", GetLastErrorAsString());. Weź również pod uwagę, że log_errorpierwszy argument jest typu std::string. (Niewidoczne) wywołanie c'tor konwersji właśnie porzuciło Twoje gwarancje uchwycenia znaczącej wartości z GetLastErrorpunktu, w którym ją wywołujesz.
Niespodziewane
Jak myślisz, dlaczego konstruktor std :: string wywołuje funkcję Win32?
Chronial
1
mallocwzywa HeapAllocdo stosu procesu. Sterta procesu jest uprawna. Jeśli potrzebuje się rozwijać, będzie on ostatecznie nazwać VirtualAlloc , który ma ustawiony kod błędu ostatniego dzwoniącego wątku. Teraz to całkowicie mija się z celem, czyli: C ++ to pole minowe. Ta implementacja tylko do tego dodaje, dostarczając interfejs z dorozumianymi gwarancjami, których po prostu nie może spełnić. Jeśli uważasz, że nie ma problemu, powinno być Ci łatwo udowodnić poprawność zaproponowanego rozwiązania. Powodzenia.
Niespodziewane
4
Jeśli potrzebujesz obsługi zarówno MBCS, jak i Unicode, odpowiedź pana C64 nie wystarczy. Bufor musi być zadeklarowany jako TCHAR i rzutowany na LPTSTR. Zauważ, że ten kod nie zajmuje się irytującym znakiem nowej linii, który Microsoft dołącza do komunikatu o błędzie.
W przypadku, gdy CStringc'tor zgłasza wyjątek, ta implementacja przecieka pamięć przydzieloną przez wywołanie FormatMessage.
Niespodziewane
To prawda, ale używam tego kodu od wielu lat i nigdy nie stanowiło to problemu. Jedynym przypadkiem, w którym ctor CString może rzucić, jest niepowodzenie przydziału pamięci, a większość kodu MFC - w tym rzeczy dostarczone przez firmę Microsoft - nie radzi sobie z warunkami braku pamięci tak wdzięcznie, jak byś chciał. Na szczęście większość komputerów ma teraz tak dużo pamięci, że musisz ciężko pracować, aby ją wykorzystać. Każde użycie, które generuje tymczasowe wystąpienie CString (w tym zwracanie CString), wiąże się z tym ryzykiem. To kompromis między ryzykiem a wygodą. Również jeśli zdarzy się to w programie obsługi komunikatów, wyjątek zostanie przechwycony.
ofiaraofleisure
Większość tego komentarza jest błędna, przepraszam. „To mi się nigdy nie przydarzyło” to cholernie słaby punkt, zwłaszcza jeśli wiesz, że kod może zawieść. Ilość pamięci nie ma również wpływu na dostępną przestrzeń adresową przydzieloną procesowi. Pamięć RAM to tylko optymalizacja wydajności. Copy-elision zapobiega przydzielaniu tymczasowego, gdy kod jest napisany, aby zezwolić NRVO. To, czy wyjątki są przechwytywane w obsłudze komunikatów, zależy od bitowości procesu i ustawień zewnętrznych. Przedstawiłem odpowiedź, z której wynika, że zarządzanie ryzykiem nie prowadzi do niedogodności.
Niespodziewane
Poza tym ryzyko wyczerpania pamięci przy tworzeniu tymczasowego jest dyskusyjne. W tym momencie wszystkie zasoby zostały uwolnione i nic złego z tego nie wyniknie.
Niespodziewane
1
Nie, przykro mi, że kod nie jest dla Ciebie przydatny. Ale TBH jest dość nisko na mojej liście problemów.
ofiara relaksu
3
voidWinErrorCodeToString(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);
}
}
Problemy z tą implementacją: 1brak obsługi Unicode. 2Niewłaściwe formatowanie komunikatu o błędzie. Jeśli wywołujący musi przetworzyć zwrócony ciąg, może to zrobić. Twoja implementacja nie pozostawia rozmówcy żadnej opcji. 3Stosowanie wyjątków, ale brak odpowiedniego zabezpieczenia wyjątków. W przypadku, gdy std::stringoperatorzy zgłaszają wyjątki, bufor przydzielony przez FormatMessagejest przeciekiem. 4Dlaczego po prostu nie zwrócić a std::stringzamiast kazać wywołującemu przekazać obiekt przez referencję?
Niespodziewane
0
Zostawię to tutaj, ponieważ będę musiał go później użyć. Jest to źródło małego, kompatybilnego binarnie narzędzia, które będzie działać równie dobrze w asemblerze, C i C ++.
GetErrorMessageLib.c (skompilowany do GetErrorMessageLib.dll)
przykład użycia z zestawem GNU, tak jak w MinGW32 (ponownie założyliśmy, że kod błędu jest prawidłowy, w przeciwnym razie potrzebne jest sprawdzenie -1).
To naprawdę nie dodaje niczego użytecznego. Co więcej, nazywa wersję ANSI FormatMessagebez wyraźnego powodu i arbitralnie ogranicza się do 80 znaków, ponownie, bez żadnego powodu. Obawiam się, że to nie jest pomocne.
Niespodziewane
masz rację Miałem nadzieję, że nikt nie zauważy braku wersji Unicode. Sprawdzę, jak zdefiniować ciąg Unicode w gnu jako i zmodyfikuję moje rozwiązanie. przepraszam za nieuczciwość.
Dmitry
ok wersja Unicode jest dostępna. i to nie jest arbitralny powód; wszystkie komunikaty o błędach mają mniej niż 80 znaków lub nie są warte czytania, a kod błędu jest ważniejszy niż komunikat o błędzie. Nie ma standardowych komunikatów o błędach, które przekraczają 80 znaków, więc jest to bezpieczne założenie, a jeśli tak nie jest, nie powoduje wycieku pamięci.
Dmitry
3
„wszystkie komunikaty o błędach mają mniej niż 80 znaków lub nie są warte czytania” - to źle. Komunikat o błędzie dla ERROR_LOCK_VIOLATION(33) to: „Proces nie może uzyskać dostępu do pliku, ponieważ inny proces zablokował część pliku”. Jest to wyraźnie dłuższe niż 80 znaków i bardzo warte przeczytania, jeśli próbujesz rozwiązać problem i znaleźć go w pliku dziennika diagnostycznego. Ta odpowiedź nie dodaje żadnej istotnej wartości do istniejących odpowiedzi.
Niespodziewane
To nie mniej naiwne. Po prostu rzadziej zawodzi. Z wyjątkiem implementacji asemblera, która dotyczy rozmiaru bufora (twierdzi, że ma miejsce na 200 znaków, gdy ma miejsce tylko na 100). Ponownie, nie dodaje to niczego istotnego, nie ma tego w żadnej z innych odpowiedzi. W szczególności jest to gorsze niż ta odpowiedź . Zobacz listę wypunktowaną tam i obserwuj, jakie problemy ma Twoja proponowana implementacja, oprócz tych, które właśnie wskazałem.
@err,hr
zegarek, a debugger automatycznie konwertuje ostatni kod błędu na reprezentację czytelną dla człowieka. Specyfikator,hr
formatu działa dla każdego wyrażenia, którego wynikiem jest wartość całkowita, np.5,hr
Zegarek wyświetli „ERROR_ACCESS_DENIED: Odmowa dostępu”. .GetLastError()
dokumentacji: „ Aby uzyskać ciąg błędu dla kodów błędów systemu, użyjFormatMessage()
funkcji. ”. Zobacz przykład pobierania kodu ostatniego błędu w witrynie MSDN.Odpowiedzi:
//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; }
źródło
(LPSTR)&messageBuffer
w tym przypadku musisz przejść , ponieważ w przeciwnym razie FormatMessageA nie może zmienić swojej wartości, aby wskazywała na przydzielony bufor.strsafe.h
nagłówek, który wcale nie jest bezpieczny , powodując kilka błędów kompilatora wwinuser.h
iwinbase.h
.1
GetLastError
jest potencjalnie wywoływany za późno.2
Brak obsługi Unicode.3
Stosowanie wyjątków bez wdrażania wyjątkowych gwarancji bezpieczeństwa.Zaktualizowano (11/2017) w celu uwzględnienia niektórych uwag.
Prosty przykład:
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);
źródło
1
brak określenia ważnejFORMAT_MESSAGE_IGNORE_INSERTS
flagi.2
GetLastError
potencjalnie wezwany za późno.3
Arbitralne ograniczenie wiadomości do 256 jednostek kodu.4
Brak obsługi błędów.256
zamiast(sizeof(buf) / sizeof(wchar_t)
? czy to jest akceptowalne i bezpieczne?MSDN zawiera przykładowy kod, który pokazuje, jak używać
FormatMessage()
iGetLastError()
razem: Pobieranie kodu ostatniego błęduźródło
GetLastError zwraca numeryczny kod błędu. Aby uzyskać opisowy komunikat o błędzie (np. Aby wyświetlić go użytkownikowi), możesz wywołać 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); }
W C ++ możesz znacznie uprościć interfejs, używając klasy 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."); } }
UWAGA: Te funkcje działają również dla wartości HRESULT. Po prostu zmień pierwszy parametr z DWORD dwErrorCode na HRESULT hResult. Reszta kodu może pozostać niezmieniona.
Te implementacje zapewniają następujące ulepszenia w stosunku do istniejących odpowiedzi:
FORMAT_MESSAGE_IGNORE_INSERTS
flagi. Aby uzyskać więcej informacji, zobacz Znaczenie flagi FORMAT_MESSAGE_IGNORE_INSERTS .Ta odpowiedź została uwzględniona w dokumentacji przepełnienia stosu. Następujący użytkownicy przyczynili się do powstania przykładu: stackptr , Ajay , Cody Gray ♦ , IInspectable .
źródło
std::runtime_error
, proponuję rzucić,std::system_error(lastError, std::system_category(), "Failed to retrieve error message string.")
gdzielastError
byłaby wartość zwracanaGetLastError()
po nieudanymFormatMessage()
wywołaniu.FormatMessage
bezpośrednio?::HeapFree(::GetProcessHeap(), 0, p)
w usuwaniu zamiast tego,::LocalFree(p)
co sugeruje dokumentacja? 2) Zdaję sobie sprawę, że dokumentacja mówi, żeby to zrobić w ten sposób, ale niereinterpret_cast<LPTSTR>(&psz)
narusza ścisłej zasady aliasingu?HeapFree
zostało wprowadzone, podczas gdy był to nadal temat w Dokumentacji SO. Będę musiał zbadać (chociaż dokumentacja wydaje się być jasna, że nie jest to bezpieczne). Pytanie 2): To jest dwojakie. W przypadku konfiguracji MBCSLPTSTR
jest aliasem dlachar*
. Ta obsada jest zawsze bezpieczna. W przypadku konfiguracji Unicode, rzutowanie dowchar_t*
jest w każdym razie podejrzane. Nie wiem, czy jest to bezpieczne w C, ale najprawdopodobniej nie jest w C ++. Też musiałbym to zbadać.FormatMessage zamieni wartość całkowitą GetLastError w wiadomość tekstową.
źródło
Ogólnie rzecz biorąc, należy użyć programu
FormatMessage
do konwersji kodu błędu Win32 na tekst.Z dokumentacji MSDN :
Deklaracja 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 );
źródło
Jeśli używasz C #, możesz użyć tego kodu:
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(); } } }
źródło
Od C ++ 11 możesz użyć standardowej biblioteki zamiast
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); } }
źródło
GetLastError
daje znaczący wynik. Ponieważ jest to C ++, jedyną bezpieczną opcją jest podanie przez dzwoniącego ostatniego kodu błędu. Z pewnością nie pomaga, że przedstawiony kod wywołujeGetLastError
dwa razy . Ponadto, choć wygodny, nieodłączny brak obsługi szerokich znaków w C ++ nie czynierror_category
interfejsu uniwersalnym. To tylko dodaje do długiej historii straconych szans w C ++.GetLastError
. Ale nie widzę różnicy między dzwonieniemGetLastError
tutaj a dzwonieniem przez dzwoniącego. Odnośnie C ++ i wchar: Nie trać nadziei, Microsoft zaczyna zezwalać aplikacjom tylko na UTF-8 .log_error("error", GetLastErrorAsString());
. Weź również pod uwagę, żelog_error
pierwszy argument jest typustd::string
. (Niewidoczne) wywołanie c'tor konwersji właśnie porzuciło Twoje gwarancje uchwycenia znaczącej wartości zGetLastError
punktu, w którym ją wywołujesz.malloc
wzywaHeapAlloc
do stosu procesu. Sterta procesu jest uprawna. Jeśli potrzebuje się rozwijać, będzie on ostatecznie nazwać VirtualAlloc , który ma ustawiony kod błędu ostatniego dzwoniącego wątku. Teraz to całkowicie mija się z celem, czyli: C ++ to pole minowe. Ta implementacja tylko do tego dodaje, dostarczając interfejs z dorozumianymi gwarancjami, których po prostu nie może spełnić. Jeśli uważasz, że nie ma problemu, powinno być Ci łatwo udowodnić poprawność zaproponowanego rozwiązania. Powodzenia.Jeśli potrzebujesz obsługi zarówno MBCS, jak i Unicode, odpowiedź pana C64 nie wystarczy. Bufor musi być zadeklarowany jako TCHAR i rzutowany na LPTSTR. Zauważ, że ten kod nie zajmuje się irytującym znakiem nowej linii, który Microsoft dołącza do komunikatu o błędzie.
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; }
Ponadto dla zwięzłości przydatna jest następująca metoda:
CString GetLastErrorString() { return FormatErrorMessage(GetLastError()); }
źródło
CString
c'tor zgłasza wyjątek, ta implementacja przecieka pamięć przydzieloną przez wywołanieFormatMessage
.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); } }
źródło
1
brak obsługi Unicode.2
Niewłaściwe formatowanie komunikatu o błędzie. Jeśli wywołujący musi przetworzyć zwrócony ciąg, może to zrobić. Twoja implementacja nie pozostawia rozmówcy żadnej opcji.3
Stosowanie wyjątków, ale brak odpowiedniego zabezpieczenia wyjątków. W przypadku, gdystd::string
operatorzy zgłaszają wyjątki, bufor przydzielony przezFormatMessage
jest przeciekiem.4
Dlaczego po prostu nie zwrócić astd::string
zamiast kazać wywołującemu przekazać obiekt przez referencję?Zostawię to tutaj, ponieważ będę musiał go później użyć. Jest to źródło małego, kompatybilnego binarnie narzędzia, które będzie działać równie dobrze w asemblerze, C i C ++.
GetErrorMessageLib.c (skompilowany do 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; } }
wersja wbudowana (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 */
dynamiczny przypadek użycia (założono, że kod błędu jest prawidłowy, w przeciwnym razie potrzebne jest sprawdzenie -1):
#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; }
zwykły przypadek użycia (zakłada, że kod błędu jest prawidłowy, w przeciwnym razie potrzebne jest sprawdzenie zwrotu -1):
#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; }
przykład użycia z zestawem GNU, tak jak w MinGW32 (ponownie założyliśmy, że kod błędu jest prawidłowy, w przeciwnym razie potrzebne jest sprawdzenie -1).
.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
wynik:
The process cannot access the file because another process has locked a portion of the file.
źródło
FormatMessage
bez wyraźnego powodu i arbitralnie ogranicza się do 80 znaków, ponownie, bez żadnego powodu. Obawiam się, że to nie jest pomocne.ERROR_LOCK_VIOLATION
(33) to: „Proces nie może uzyskać dostępu do pliku, ponieważ inny proces zablokował część pliku”. Jest to wyraźnie dłuższe niż 80 znaków i bardzo warte przeczytania, jeśli próbujesz rozwiązać problem i znaleźć go w pliku dziennika diagnostycznego. Ta odpowiedź nie dodaje żadnej istotnej wartości do istniejących odpowiedzi.