Як отримати повідомлення про помилку з коду помилки, поверненого GetLastError ()?


138

Як я можу отримати останнє повідомлення про помилку після виклику Windows API?

GetLastError() повертає ціле значення, а не текстове повідомлення.


у розділі інструментів візуальної студії використовується пошук помилок EXE, які роблять це досить добре, коли для налагодження вам потрібно лише повідомлення від помилки.
ColdCat

@ColdCat: Для налагодження набагато простіше просто додати @err,hrгодинник, і налагоджувач автоматично перетворить останній код помилки в читане для людини представлення. Специфікатор ,hrформату працює для будь-якого виразу, що оцінює інтегральне значення, наприклад, на 5,hrгодиннику відображатиметься "ERROR_ACCESS_DENIED: Доступ заборонено" .
Неочікувана

2
З GetLastError()документації: " Щоб отримати рядок помилок для системних кодів помилок, використовуйте FormatMessage()функцію. " Дивіться приклад отримання коду останньої помилки на MSDN.
Ремі Лебо

Відповіді:


145
//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;
}

2
Я вважаю, що вам насправді потрібно пройти (LPSTR)&messageBufferв цьому випадку, оскільки в іншому випадку FormatMessageA не може змінити своє значення, щоб вказати на виділений буфер.
Кілотан

2
О, вау, так, це дивно дивно. Як би змінити покажчик? Але передаючи йому адресу вказівника (покажчик-на-вказівник), але перекладаючи його на звичайний вказівник ... Дивацтво Win32. Дякую за голову вгору, зафіксував це у власній базі коду (і моїй відповіді). Дуже тонкий улов.
Джамін Грей

1
Дякую вам, ваш приклад набагато зрозуміліший, ніж приклад із MSDN. Більше того, той з MSDN навіть не зміг скласти. Він включає в себе деякий strsafe.hзаголовок, який зовсім не безпечний , він викликає купу помилок компілятора в winuser.hі winbase.h.
Привіт-Ангел

2
Деякі ідентифікатори помилок не підтримуються. Наприклад, 0x2EE7, ERROR_INTERNET_NAME_NOT_RESOLVED викликає нову помилку при виклику FormatMessage: 0x13D, Система не може знайти текст повідомлення для номера повідомлення 0x% 1 у файлі повідомлення для% 2.
Brent

3
Проблеми з цією реалізацією: 1 GetLastErrorпотенційно називають занадто пізно. 2Немає підтримки Unicode. 3Використання винятків без застосування гарантій безпеки виключень.
Неочікувана

65

Оновлено (11/2017), щоб врахувати деякі коментарі.

Простий приклад:

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

3
@ Hi-Angel - Приклад передбачає, що ви компілюєте з визначеним UNICODE. 'FormatMessage' - це фактично макрос, який розширюється до 'FormatMessageA' для буферів символів Ansi / MBCS, або до 'FormatMessageW' для буферів UTF16 / UNICODE, залежно від способу складання програми. Я вирішив редагувати приклад вище, щоб явно викликати версію, яка відповідає типу вихідного буфера (wchar_t).
Bukes

2
Додайте FORMAT_MESSAGE_IGNORE_INSERTS: "Якщо ви не контролюєте рядок формату, тоді вам слід передати FORMAT_MESSAGE_IGNORE_INSERTS, щоб% 1 не спричинила проблем." blogs.msdn.microsoft.com/oldnewthing/20071128-00/?p=24353
Рой Дантон

1
Проблеми з цією реалізацією: 1Невказання важливого FORMAT_MESSAGE_IGNORE_INSERTSпрапора. 2 GetLastErrorпотенційно називається занадто пізно. 3Довільне обмеження повідомлення на 256 кодових одиниць. 4Немає обробки помилок.
Неочікувана

1
sizeof (buf) повинен бути ARRAYSIZE (buf), оскільки FormatMessage очікує розміру буфера в TCHAR, а не в байтах.
Кай

1
Чи не можемо ми використати 256замість цього (sizeof(buf) / sizeof(wchar_t)? це прийнятно і безпечно?
BattleTested



14

GetLastError повертає числовий код помилки. Для отримання описового повідомлення про помилку (наприклад, для відображення користувачеві) ви можете зателефонувати в 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);
}

У C ++ ви можете значно спростити інтерфейс за допомогою класу 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.");
    }
}

ПРИМІТКА. Ці функції також працюють для значень HRESULT. Просто змініть перший параметр з DWORD dwErrorCode на HRESULT hResult. Решта коду може залишитися незмінною.


Ці реалізації забезпечують такі покращення щодо існуючих відповідей:

  • Повний зразок коду, а не лише посилання на API для дзвінка.
  • Забезпечує як C, так і C ++ реалізацію.
  • Працює як для налаштувань проекту Unicode, так і для MBCS.
  • Приймає код помилки як вхідний параметр. Це важливо, оскільки останній код помилки потоку діє лише у чітко визначених точках. Вхідний параметр дозволяє абоненту дотримуватися документально підтвердженого контракту.
  • Забезпечує належну безпеку виключень. На відміну від усіх інших рішень, які неявно використовують винятки, ця реалізація не просочиться в пам'яті у випадку, якщо буде викинуто виняток під час побудови повернутого значення.
  • Правильне використання FORMAT_MESSAGE_IGNORE_INSERTSпрапора. Додаткову інформацію див. У важливості прапора FORMAT_MESSAGE_IGNORE_INSERTS .
  • Правильне поводження з помилками / повідомлення про помилки, на відміну від деяких інших відповідей, які мовчки ігнорують помилки.


Ця відповідь включена із документації щодо переповнення стека. До прикладу внесли наступні користувачі: stackptr , Ajay , Cody Grey ♦ , IInspectable .


1
Замість того, щоб кидати std::runtime_error, я пропоную кинути, std::system_error(lastError, std::system_category(), "Failed to retrieve error message string.")де lastErrorбуло б значення повернення GetLastError()після невдалого FormatMessage()виклику.
zett42

Яка перевага від використання вашої версії C FormatMessageбезпосередньо перед безпосередньою?
Micha Wiedenmann

@mic: Це як запитати: "Яка перевага функцій у C?" Функції зменшують складність, надаючи абстракції. У цьому випадку абстракція зменшує кількість параметрів з 7 до 3, реалізує документований контракт API шляхом передачі сумісних прапорів та параметрів та кодує задокументовану логіку повідомлення про помилки. Він пропонує стислий інтерфейс, легко розгадати семантику, що дозволяє вам вкласти 11 хвилин, щоб прочитати документацію .
Неочікуваний

Мені подобається ця відповідь, дуже ясно, що ретельна увага приділяється правильності коду. У мене два питання. 1) Чому ви використовуєте ::HeapFree(::GetProcessHeap(), 0, p)в делетері замість того, ::LocalFree(p)як підказує документація? 2) Я усвідомлюю, що документація говорить, що робити це так, але це не reinterpret_cast<LPTSTR>(&psz)порушує суворого правила дозволу?
Teh JoE

@teh: Дякую за відгук. Питання 1) Чесно кажучи, я не знаю, чи безпечно це. Я не пригадую, до кого або чому HeapFreeбуло запроваджено дзвінок , хоча це ще було темою в Документації про СО. Мені доведеться розслідувати (хоча документація здається зрозумілою, що це не безпечно). Питання 2): Це подвійно. Для конфігурації MBCS LPTSTRпсевдонім для char*. Цей акторський склад завжди безпечний. Для конфігурації Unicode будь-яка трансляція wchar_t*є рибною. Я не знаю, чи це безпечно в C, але це, швидше за все, не в C ++. Я також повинен був би розслідувати це.
IIнеочікуваний

13

Взагалі вам потрібно використовувати FormatMessageдля перетворення коду помилки Win32 в текст.

З документації MSDN :

Форматує рядок повідомлення. Функція вимагає визначення повідомлення як вхідного. Визначення повідомлення може надходити з буфера, переданого у функцію. Він може надходити з ресурсу таблиці повідомлень у вже завантаженому модулі. Або абонент може попросити функцію пошуку ресурсів (таблиць) системи повідомлень для визначення повідомлення. Функція знаходить визначення повідомлення в ресурсі таблиці повідомлень на основі ідентифікатора повідомлення та ідентифікатора мови. Функція копіює відформатований текст повідомлення у вихідний буфер, обробляючи будь-які вбудовані послідовності вставок, якщо це вимагається.

Декларація 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
);

7

Якщо ви використовуєте c #, ви можете використовувати цей код:

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

Я підтвердив, що цей код працює. Чи не повинен TS прийняти цю відповідь?
swdev

2
Якщо це потрібно для подальшого кидання, є більш простий спосіб зробити це в C # за допомогою Win32Exception
SerG

2
@swdev: Чому хтось повинен приймати відповідь на C # на запитання з тегом c або c ++ ? На даний момент ця відповідь навіть не стосується питання, яке задають.
Неочікувана

1
Ну, я не пригадую, щоб я голосував за цю запропоновану відповідь. Я вирішив очевидний недолік у логіці @ swdev. Але оскільки ви не збираєтесь мені вірити, я зараз вам це докажу: ось, ще один голосування. Це одне від мене, тому що ця відповідь - хоча вона може бути корисною з іншого питання - просто не корисна, враховуючи питання, яке задавали.
Неочікувана

2
"Я думаю, ви маєте корисні відомості, щоб запропонувати поза очевидним" - Дійсно, маю .
Неочікувана

4

Якщо вам потрібно підтримувати MBCS, а також Unicode, відповіді Mr.C64 недостатньо. Буфер повинен бути оголошений TCHAR та переданий до LPTSTR. Зауважте, що цей код не стосується дратівливого нового рядка, який Microsoft додає до повідомлення про помилку.

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

Також для стислості я вважаю корисним наступний метод:

CString GetLastErrorString()
{
    return FormatErrorMessage(GetLastError());
}

У випадку, якщо CStringc'tor видає виняток, ця реалізація просочує пам'ять, виділену викликом FormatMessage.
Неочікувана

Щоправда, але я використовував цей код багато років, і це ніколи не було проблемою. Єдиний випадок, коли, швидше за все, кидає ctor CString, - це невміння розподілити пам'ять, і більшість MFC-кодів, включаючи матеріали, надані Microsoft - не справляється з умовами пам'яті так витончено, як вам би хотілося. На щастя, у більшості ПК зараз стільки пам’яті, що вам доведеться працювати надто важко, щоб використовувати це все. Будь-яке використання, яке генерує тимчасовий екземпляр CString (включаючи повернення CString), ризикує цим ризиком. Це компроміс із ризиком / зручністю. Крім того, якщо це трапиться в обробці повідомлень, виняток буде потрапляти.
victimofleisure

Більшість цього коментаря помилкові, вибачте. "Мені ніколи не бувало" - це проклята слабка сторона, особливо коли ти знаєш, як код може вийти з ладу. Обсяг пам’яті не впливає і на доступний адресний простір, відведений також процесу. ОЗУ - це лише оптимізація продуктивності. Copy-elision запобігає виділенню тимчасового, коли код записується для дозволу NRVO. Будь-які винятки потрапляють у обробник повідомлень, залежить від бітовості процесу та зовнішніх налаштувань. Я надіслав відповідь, яка свідчить, що управління ризиками не призводить до незручностей.
IIнеочікуваний

Крім того, ризик втрати пам’яті при побудові тимчасового є суперечливим. Тоді всі ресурси були звільнені, і нічого поганого з цього не вийде.
ІІНеочікувана

1
Ні, вибачте, що код вам не корисний. Але TBH це досить низько в моєму списку проблем.
victimofleisure

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

Чи можете ви також додати пояснення?
Роберт

1
Проблеми з цією реалізацією: 1Немає підтримки Unicode. 2Невідповідне форматування повідомлення про помилку. Якщо абонент повинен обробити повернутий рядок, він може це зробити. Ваша реалізація залишає абонента без можливості. 3Використання винятків, але відсутність належної безпеки виключень. У випадку, якщо std::stringоператори скидають винятки, буфер, що виділяється, FormatMessageпротікає. 4Чому б просто не повернути std::stringзамість того, щоб абонент передав об'єкт за посиланням?
Неочікувана

1

Оскільки c ++ 11, ви можете використовувати стандартну бібліотеку замість 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);
    }
}

Є лише крихітне вікно, де виклик GetLastErrorдає значущий результат. Оскільки це C ++, єдиний безпечний варіант - дозволити абоненту надати останній код помилки. Це, звичайно, не допомагає, що представлений код дзвонить GetLastError двічі . Крім того, незважаючи на те, що C ++, властива відсутність широкої підтримки символів, не робить error_categoryінтерфейс корисним. Це просто доповнює довгу історію пропущених можливостей C ++.
ІІнеочікуване

Хороший момент про марний заклик GetLastError. Але я не бачу різниці між дзвінками GetLastErrorсюди або тим, хто телефонує. Що стосується C ++ та wchar: Не відмовляйтесь від надії, Microsoft починає дозволяти програмам бути лише UTF-8 .
Хронічний

«Я не бачу ніякої різниці» - Розглянемо наступний сайт виклику: log_error("error", GetLastErrorAsString());. Також врахуйте, що log_errorперший аргумент має тип std::string. (Невидимий) виклик c'tor перетворення просто знизив ваші гарантії на отримання значущого значення з GetLastErrorмоменту, коли ви його називаєте .
ІІнеочікуване

Чому ви вважаєте, що конструктор std :: string викликає функцію Win32?
Хронічний

1
mallocзакликає HeapAllocкупувати процес. Купа технологічних виробів. Якщо він повинен рости, це буде в кінцевому рахунку називає VirtualAlloc , що робить набір коду останньої помилки викликає потоку. Тепер це зовсім не вистачає суті, а саме: C ++ - це мінне поле. Ця реалізація просто доповнює це, надаючи інтерфейс із неявними гарантіями, який він просто не може реалізувати. Якщо ви вважаєте, що проблем немає, вам доведеться легко довести правильність запропонованого рішення. Удачі.
ІІнеочікуване

0

Я залишу це тут, оскільки мені потрібно буде використовувати його пізніше. Це джерело невеликого сумісного двійкового інструменту, який однаково добре працюватиме в збірці, C і C ++.

GetErrorMessageLib.c (компілюється в 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;
    }
}

вбудована версія (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 */

динамічний кодекс використання (припускається, що код помилки дійсний, інакше потрібна перевірка -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;
}

випадок регулярного використання (передбачається, що код помилки дійсний, інакше потрібно -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;
}

Наприклад, використовуючи за допомогою gnu Assembly як у MinGW32 (знову ж таки, припускаємо, що код помилки дійсний, інакше потрібно -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

результат: The process cannot access the file because another process has locked a portion of the file.


1
Це дійсно не додає нічого корисного. Крім цього, він називає версію ANSI FormatMessageбез видимих ​​причин і довільно обмежує себе на 80 символів, знову ж таки, без жодної причини. Боюся, це не корисно.
Неочікувана

ви праві, я сподівався, що ніхто не помітить про відсутність версії Unicode. Я перевірю, як визначити рядок Unicode в gnu як і змінити своє рішення. вибачте за нечесність.
Дмитро

Ок, версія Unicode працює. і це не довільна причина; всі повідомлення про помилку мають менше 80 символів або їх не варто читати, а код помилки важливіший, ніж повідомлення про помилку. Немає стандартних повідомлень про помилки, що перевищують 80 символів, тому це безпечне припущення, а коли це не так, воно не просочує пам'ять.
Дмитро

3
"всі повідомлення про помилки мають менше 80 символів, або їх не варто читати" - Це неправильно. Повідомлення про помилку ERROR_LOCK_VIOLATION(33): "Процес не може отримати доступ до файлу, оскільки інший процес заблокував частину файлу." Це однозначно довше 80 символів, і дуже варто прочитати, якщо ви намагаєтеся вирішити проблему і знайти це у файлі журналу діагностики. Ця відповідь не додає суттєвого значення існуючим відповідям.
Неочікувана

Це не менш наївно. Це просто виходить з ладу рідше. За винятком реалізації монтажу, яка полягає в розмірі буфера (стверджуючи, що має місце на 200 символів, коли в ньому є лише місце для 100). Знову ж таки, це не додає нічого істотного, цього немає вже в жодній з інших відповідей. Зокрема, це гірше, ніж ця відповідь . Дивіться там список списку та спостерігайте, які проблеми вашої запропонованої реалізації є, крім тих, які я щойно зазначив.
Неочікувана
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.