Jak uzyskać komunikat o błędzie z kodu błędu zwróconego przez GetLastError ()?

144

Jak po wywołaniu interfejsu API systemu Windows mogę uzyskać ostatni komunikat o błędzie w formie tekstowej?

GetLastError() zwraca wartość całkowitą, a nie wiadomość tekstową.

Micha Wiedenmann
źródło
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”. .
Niespodziewane
2
Z GetLastError()dokumentacji: „ Aby uzyskać ciąg błędu dla kodów błędów systemu, użyj FormatMessage()funkcji. ”. Zobacz przykład pobierania kodu ostatniego błędu w witrynie MSDN.
Remy Lebeau

Odpowiedzi:

151
//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;
}
Jamin Gray
źródło
2
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ą: 1 GetLastErrorjest 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.

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);
LeviX
źródło
3
@ 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).
Bukes
2
Dodaj FORMAT_MESSAGE_IGNORE_INSERTS: "Jeśli nie masz kontroli nad ciągiem formatującym, musisz przekazać FORMAT_MESSAGE_IGNORE_INSERTS, aby% 1 nie powodował problemów." blogs.msdn.microsoft.com/oldnewthing/20071128-00/?p=24353
Roi Danton
1
Problemy z tą implementacją: 1brak określenia ważnej FORMAT_MESSAGE_IGNORE_INSERTSflagi. 2 GetLastErrorpotencjalnie 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?
BattleTested
17

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:

  • 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ściwe użycie FORMAT_MESSAGE_IGNORE_INSERTSflagi. Aby uzyskać więcej informacji, zobacz Znaczenie flagi FORMAT_MESSAGE_IGNORE_INSERTS .
  • 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 .

Niespodziewane
źródło
1
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ć.
Niespodziewane
13

Ogólnie rzecz biorąc, należy użyć programu FormatMessagedo konwersji kodu błędu Win32 na tekst.

Z dokumentacji MSDN :

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.

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
);
Vinay Sajip
źródło
8

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();
        }
    }
}
rboy
źródło
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::string GetLastErrorAsString(){
    DWORD errorMessageID = ::GetLastError();
    if (errorMessageID == 0) {
        return std::string(); //No error message has been recorded
    } else {
        return std::system_category().message(errorMessageID);
    }
}
Chronial
źródło
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 GetLastError dwa 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.

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());
}
ofiara wolnego czasu
źródło
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
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);
}
}
Andriy Kuz
źródło
Czy mógłbyś również dodać jakieś wyjaśnienie?
Robert,
1
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)

#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.

Dmitrij
źródło
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.
Niespodziewane