Как получить сообщение об ошибке из кода ошибки, возвращенного GetLastError ()?

138

Как я могу получить последнее сообщение об ошибке в текстовой форме после вызова Windows API?

GetLastError() возвращает целочисленное значение, а не текстовое сообщение.

Миха Виденманн
источник
Раньше в разделе инструментов в Visual Studio выполнялся поиск ошибок exe, который делает это довольно хорошо, когда для отладки нужно только сообщение об ошибке.
ColdCat
@ColdCat: Для отладки намного проще просто добавить @err,hrчасы, и отладчик автоматически преобразует код последней ошибки в удобочитаемое представление. Спецификатор ,hrформата работает для любого выражения, которое оценивается как целое значение, например, 5,hrчасы будут отображать «ERROR_ACCESS_DENIED: доступ запрещен». ,
2
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 не может изменить свое значение, чтобы указать на выделенный буфер.
Kylotan
2
Ого, да, это довольно странно. Как бы он изменил указатель? Но передав ему адрес указателя (pointer-to-a-pointer), но приведя его к обычному указателю ... Странность 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Использование исключений без применения гарантий безопасности исключений.
2
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);
LeviX
источник
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Нет обработки ошибок.
2
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 .

IInspectable
источник
1
Вместо того, чтобы бросать std::runtime_error, я предлагаю бросить, std::system_error(lastError, std::system_category(), "Failed to retrieve error message string.")где lastErrorбудет возвращаемое значение GetLastError()после неудачного FormatMessage()вызова.
zett42
В чем преимущество использования вашей версии C перед FormatMessageпрямой?
Миха Виденманн
@mic: Это все равно что спросить: «В чем преимущество функций в C?» Функции уменьшают сложность, предоставляя абстракции. В этом случае абстракция сокращает число параметров с 7 до 3, реализует документированный контракт API, передавая совместимые флаги и параметры, и кодирует документированную логику сообщения об ошибках. Он обеспечивает лаконичный интерфейс с легко угадываемой семантикой, избавляя вас от необходимости тратить 11 минут на чтение документации .
Инспектируемый
Мне нравится этот ответ, очень ясно, что правильность кода была уделена пристальное внимание. У меня два вопроса. 1) Почему вы используете ::HeapFree(::GetProcessHeap(), 0, p)в удалителе вместо того, ::LocalFree(p)что предлагается в документации? 2) Я понимаю, что документация говорит, что нужно делать это таким образом, но не reinterpret_cast<LPTSTR>(&psz)нарушает ли строгое правило псевдонимов?
Teh JOE
@teh: Спасибо за отзыв. Вопрос 1): Честно говоря, я не знаю, безопасно ли это. Я не помню, кто или почему HeapFreeбыл призван, хотя это было еще одной темой в документации SO. Я должен исследовать (хотя документация, кажется, ясно, что это не безопасно). Вопрос 2): это в два раза. Для конфигурации MBCS LPTSTRэто псевдоним для char*. Этот состав всегда безопасен. Для конфигурации Unicode, приведение к wchar_t*является подозрительным в любом случае. Я не знаю, безопасно ли это в C, но, скорее всего, не в C ++. Мне бы тоже пришлось это расследовать.
2
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();
        }
    }
}
rboy
источник
Я подтвердил, что этот код работает. Разве TS не должен принять этот ответ?
swdev
2
Если это необходимо для дальнейшего броска, есть более простой способ сделать это в C # с Win32Exception
SerG
2
@swdev: Почему кто-то должен принять ответ в C # на вопрос, помеченный c или c ++ ? В нынешнем виде этот ответ даже не касается задаваемого вопроса.
2
1
Ну, я не помню, чтобы голосовал по этому предложенному ответу. Я обратил внимание на очевидный недостаток логики @ swdev. Но так как вы не поверите мне, я сейчас докажу вам: вот, еще один голос. Этот ответ от меня, потому что этот ответ - хотя он может быть полезен при задании другого вопроса - просто бесполезен при заданном вопросе.
2
2
«Я полагаю, у вас есть полезные идеи, которые вы можете предложить за пределами очевидного» - действительно, у меня есть .
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());
}
victimofleisure
источник
В случае, если CStringc'tor выдает исключение, эта реализация приводит к утечке памяти, выделенной вызовом FormatMessage.
Inspectable 08
Правда, но я использовал этот код много лет, и это никогда не было проблемой. Единственный случай, когда ctring ctor, скорее всего, выбрасывает, это сбой в распределении памяти, и большая часть кода MFC, включая предоставляемые Microsoft материалы, не справляется с нехваткой памяти так изящно, как вам бы этого хотелось. К счастью, у большинства ПК сейчас так много памяти, что вам приходится много работать, чтобы использовать все это. Любое использование, которое генерирует временный экземпляр CString (включая возврат CString), сопряжено с этим риском. Это компромисс между риском и удобством. Также, если это происходит в обработчике сообщений, исключение будет перехвачено.
жертва досуга
Большая часть этого комментария неверна, извините. «Это никогда не случалось со мной» - чертовски слабое место, особенно когда вы знаете, как код может потерпеть неудачу. Объем памяти также не влияет на доступное адресное пространство, выделенное процессу. RAM - это просто оптимизация производительности. Copy-elision предотвращает выделение временного, когда код написан для разрешения NRVO. Будут ли исключения обработаны в обработчике сообщений, зависит от разрядности процесса и внешних настроек. Я представил ответ, который показывает, что управление рисками не приводит к неудобствам.
Инспектируемое
Кроме того, риск нехватки памяти при создании временного не имеет значения. На тот момент все ресурсы были освобождены, и ничего плохого из этого не выйдет.
II
1
Нет, извините, код не полезен для вас. Но TBH это довольно низко в моем списке проблем.
жертва досуга
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Почему бы просто не вернуть a std::stringвместо того, чтобы вызывающая сторона передала объект по ссылке?
2
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);
    }
}
Chronial
источник
Есть только крошечное окно, в котором вызов GetLastErrorдает значимый результат. Поскольку это C ++, единственный безопасный вариант здесь - предоставить вызывающему абоненту последний код ошибки. То, что код представил вызовы GetLastError дважды, конечно не помогает . Кроме того, несмотря на удобство, присущее C ++ отсутствие поддержки широких символов не делает error_categoryинтерфейс универсальным. Это лишь добавляет к долгой истории упущенных возможностей C ++.
Inspectable
Хороший вопрос о бесполезном призыве к GetLastError. Но я не вижу разницы между GetLastErrorзвонком сюда или вызовом вызывающего абонента. Что касается C ++ и wchar: не оставляйте надежды, Microsoft начинает разрешать приложениям только UTF-8 .
Хрони
«Я не вижу никакой разницы» - Рассмотрим следующий сайт вызова: log_error("error", GetLastErrorAsString());. Также учтите, что log_errorпервый аргумент имеет тип std::string. Вызов (невидимое) преобразования c'or просто лишил вас гарантий получить значимое значение с 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, как в 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 символов, опять же, без какой-либо причины. Боюсь, это бесполезно.
Inspectable
Вы правы, я надеялся, что отсутствие версии Unicode никто не заметит. Я проверю, как определить строку Unicode в gnu as, и изменю свое решение. извините за нечестность.
Дмитрий
ОК версия Unicode установлена. и это не произвольная причина; все сообщения об ошибках содержат менее 80 символов или их не стоит читать, а код ошибки более важен, чем сообщение об ошибке. Стандартных сообщений об ошибках, длина которых превышает 80 символов, не существует, так что это безопасное предположение, а когда это не так, утечки памяти не происходит.
Дмитрий
3
«все сообщения об ошибках либо меньше 80 символов, либо не стоит читать» - это неправильно. Сообщение об ошибке для ERROR_LOCK_VIOLATION(33): «Процесс не может получить доступ к файлу, потому что другой процесс заблокировал часть файла». Это явно длиннее 80 символов, и их очень стоит прочитать, если вы пытаетесь решить проблему и найти это в файле журнала диагностики. Этот ответ не добавляет существенной ценности по сравнению с существующими ответами.
Inspectable
Это не менее наивно. Просто он реже терпит неудачу. За исключением реализации сборки, которая заключается в размере буфера (утверждающем, что у него есть место для 200 символов, когда в нем есть место только для 100). Опять же, это не добавляет ничего существенного, этого еще нет ни в одном из других ответов. В частности, это хуже, чем этот ответ . Посмотрите маркированный список и отметьте, какие проблемы есть у вашей предлагаемой реализации, помимо тех, которые я только что указал.
Inspectable