GetLastError ()에서 반환 한 오류 코드에서 오류 메시지를 얻는 방법은 무엇입니까?


138

Windows API 호출 후 텍스트 형식의 마지막 오류 메시지를 어떻게 얻을 수 있습니까?

GetLastError() 문자 메시지가 아닌 정수 값을 반환합니다.


디버깅을 위해 오류 메시지가 필요할 때 Visual Studio의 도구 섹션에 exe 오류 조회가 있습니다.
ColdCat

@ColdCat : 디버깅을 위해 단순히 @err,hr시계를 추가하는 것이 훨씬 쉽고 디버거가 마지막 오류 코드를 사람이 읽을 수있는 표현으로 자동 변환하도록합니다. ,hr형식 지정은 정수 값으로 평가는, 예를 들면 것을 어떤 식 작동 5,hr시계가 표시됩니다 "ERROR_ACCESS_DENIED :. 액세스가 거부되었습니다" .
IInspectable

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)&messageBufferFormatMessageA가 할당 된 버퍼를 가리 키도록 값을 변경할 수있는 방법이 없으므로 실제로이 경우 전달해야한다고 생각합니다 .
Kylotan

2
오, 그래, 좀 이상해 포인터를 어떻게 수정합니까? 그러나 포인터의 주소 (포인터에서 포인터로)를 전달하지만 일반 포인터로 캐스팅하면 ... Win32 기이함. 헤드 업 주셔서 감사합니다. 내 코드 기반 (및 내 대답)으로 수정했습니다. 매우 미묘한 캐치.
Jamin Grey

1
고맙습니다. 귀하의 예는 MSDN의 예보다 훨씬 명확합니다. 또한 MSDN의 컴파일조차도 컴파일 할 수 없었습니다. 여기에는 strsafe.h전혀 안전 하지 않은 헤더가 포함되어 있으며 winuser.h및 에서 많은 컴파일러 오류가 발생합니다 winbase.h.
Hi-Angel

2
일부 오류 ID는 지원되지 않습니다. 예를 들어, 0x2EE7, ERROR_INTERNET_NAME_NOT_RESOLVED는 FormatMessage를 호출 할 때 새 오류를 발생시킵니다. 0x13D, 시스템은 % 2의 메시지 파일에서 메시지 번호 0x % 1의 메시지 텍스트를 찾을 수 없습니다.
Brent

3
이 구현의 문제점 : 1 GetLastError잠재적으로 너무 늦게 호출됩니다. 2유니 코드를 지원하지 않습니다. 3예외 안전 보장을 구현하지 않고 예외 사용.
IInspectable

65

일부 의견을 고려하여 업데이트되었습니다 (2017 년 11 월).

쉬운 예 :

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'는 실제로 애플리케이션 컴파일 방법에 따라 Ansi / MBCS 문자 버퍼의 경우 'FormatMessageA'또는 UTF16 / UNICODE 버퍼의 경우 'FormatMessageW'로 확장되는 매크로입니다. 출력 버퍼 유형 (wchar_t)과 일치하는 버전을 명시 적으로 호출하기 위해 위의 예제를 자유롭게 편집 할 수있었습니다.
Bukes

2
FORMAT_MESSAGE_IGNORE_INSERTS를 추가하십시오. "포맷 문자열을 제어 할 수없는 경우 % 1이 문제를 일으키지 않도록 FORMAT_MESSAGE_IGNORE_INSERTS를 전달해야합니다." blogs.msdn.microsoft.com/oldnewthing/20071128-00/?p=24353
Roi Danton

1
이 구현과 관련된 문제 : 1중요한 FORMAT_MESSAGE_IGNORE_INSERTS플래그 를 지정하지 못했습니다 . 2 GetLastError너무 늦게 전화했을 수 있습니다. 3메시지를 256 코드 단위로 임의로 제한합니다. 4오류 처리가 없습니다.
IInspectable

1
FormatMessage는 바이트가 아닌 TCHAR 단위의 버퍼 크기를 예상하므로 sizeof (buf)는 ARRAYSIZE (buf) 여야합니다.
Kai

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 ++ 구현을 모두 제공합니다.
  • 유니 코드 및 MBCS 프로젝트 설정 모두에서 작동합니다.
  • 오류 코드를 입력 매개 변수로 사용합니다. 스레드의 마지막 오류 코드는 잘 정의 된 지점에서만 유효하므로 이는 중요합니다. 입력 매개 변수를 사용하면 호출자가 문서화 된 계약을 따를 수 있습니다.
  • 적절한 예외 안전을 구현합니다. 암시 적으로 예외를 사용하는 다른 모든 솔루션과 달리이 구현은 반환 값을 구성하는 동안 예외가 발생하는 경우 메모리가 누출되지 않습니다.
  • 의 적절한 사용 FORMAT_MESSAGE_IGNORE_INSERTS깃발 . 자세한 정보 는 FORMAT_MESSAGE_IGNORE_INSERTS 플래그의 중요성 을 참조하십시오.
  • 다른 답변과 달리 오류를 자동으로 무시하는 적절한 오류 처리 / 오류보고


이 답변은 Stack Overflow Documentation에서 통합되었습니다. 다음 사용자가 예제에 기여했습니다 : stackptr , Ajay , Cody Gray ♦ , IInspectable .


1
을 던지는 대신 실패한 호출 후의 반환 값이 될 곳 std::runtime_error을 던지는 것이 좋습니다 . std::system_error(lastError, std::system_category(), "Failed to retrieve error message string.")lastErrorGetLastError()FormatMessage()
zett42

C 버전을 FormatMessage직접 사용하는 것의 장점은 무엇입니까 ?
Micha Wiedenmann 2016 년

@mic : "C에서 함수의 장점은 무엇입니까?" 와 같은 질문입니다. 함수는 추상화를 제공하여 복잡성을 줄입니다. 이 경우 추상화는 매개 변수 수를 7에서 3으로 줄이고 호환 가능한 플래그 및 매개 변수를 전달하여 API의 문서화 된 계약을 구현하며 문서화 된 오류보고 논리를 인코딩합니다. 간결한 인터페이스로 의미를 쉽게 추측 할 수 있으며 설명서 를 읽는 데 11 분이 소요됩니다 .
IInspectable

나는이 답변을 좋아한다. 코드의 정확성에주의를 기울 였다는 것이 분명하다. 두 가지 질문이 있습니다. 1) 문서가 제안하는 ::HeapFree(::GetProcessHeap(), 0, p)대신 왜 삭제 기 에서 사용 ::LocalFree(p)합니까? 2) 문서 가이 방법으로 지시하지만 reinterpret_cast<LPTSTR>(&psz)엄격한 앨리어싱 규칙을 위반 하지 않는다는 것을 알고 있습니다 .
Teh JoE

@teh : 피드백 주셔서 감사합니다. 질문 1) : 솔직히, 이것이 안전한지 모르겠습니다. 나는 HeapFreeSO 문서에서 주제가되었지만 누가, 왜 전화를 받았는지 기억 나지 않습니다 . 조사해야 할 것입니다 (문서가 분명한 것처럼 보이지만 이것이 안전 하지는 않습니다 ). 질문 2) : 이것은 두 가지입니다. MBCS 구성의 경우 LPTSTR에 대한 별칭입니다 char*. 그 캐스트는 항상 안전합니다. 유니 코드 구성의 경우 캐스트 wchar_t*는 어쨌든 비린내가 있습니다. 이것이 C에서 안전한지 모르겠지만 C ++에서는 그렇지 않을 가능성이 큽니다. 이것도 조사해야 할 것입니다.
IInspectable

13

일반적으로 FormatMessageWin32 오류 코드에서 텍스트로 변환하는 데 사용해야 합니다.

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
추가 투척이 필요한 경우 Win32Exception으로 C #에서 더 간단한 방법 이 있습니다.
SerG

2
@ swdev : 왜 C #에서 c 또는 c ++ 태그가 붙은 질문에 대한 대답을 받아 들여야 합니까? 이 답변은 질문에 대한 답변조차하지 않습니다.
IInspectable

1
글쎄, 나는이 제안 된 답변에 투표 한 것을 기억하지 않습니다. @swdev의 논리에서 명백한 결함을 해결했습니다. 그러나 당신이 나를 믿지 않기 때문에, 나는 지금 당신에게 그것을 증명할 것입니다 : 여기, 또 다른 투표를하십시오. 이 답변은 다른 질문이 있으면 유용 할 수 있지만 요청 된 질문에는 유용하지 않기 때문에 그 사람은 나에게서 온 것입니다.
IInspectable

2
"당신이 명백한 것 이상으로 제공 할 수있는 유용한 통찰력을 가지고 있다고 생각합니다." - 실제로, 나는 .
IInspectable

4

MBCS와 유니 코드를 지원해야하는 경우 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.
IInspectable

사실, 나는 수년 동안이 코드를 사용해 왔으며 결코 문제가되지 않았습니다. CString ctor가 던질 가능성이있는 유일한 경우는 메모리 할당 실패이며 Microsoft에서 제공하는 것들을 포함한 대부분의 MFC 코드는 원하는만큼 메모리 상태를 제대로 처리하지 못합니다. 운 좋게도 대부분의 PC에는 메모리가 너무 많아서 모두 사용하기 위해 열심히 노력해야합니다. CString 반환을 포함하여 임시 CString 인스턴스를 생성하는 모든 사용은 이러한 위험을 초래합니다. 위험 / 편의의 절충입니다. 또한 메시지 핸들러에서 발생하면 예외가 발생합니다.
희생자 레저 레저 (

이 의견의 대부분은 잘못되었습니다. 죄송합니다. "그것은 결코 나에게 일어난 적이 없다" 는 특히 코드가 어떻게 실패 할 수 있는지 아는 경우에 약점입니다. 메모리 양은 프로세스에 할당 된 사용 가능한 주소 공간에 영향을 미치지 않습니다. RAM은 성능 최적화 일뿐입니다. 복사 제거는 NRVO를 허용하는 코드가 작성 될 때 임시 할당을 방지합니다. 메시지 핸들러에서 예외가 발생하는지 여부는 프로세스 비트 및 외부 설정에 따라 다릅니다. 위험 관리로 인해 불편을 겪지 않는다는 답변을 제출했습니다.
IInspectable

또한 임시 구성시 메모리가 부족할 위험이 있습니다. 이 시점에서 모든 자원이 해제되었으며 아무 것도 나올 수 없습니다.
IInspectable

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

설명을 추가해 주시겠습니까?
Robert

1
이 구현과 관련된 문제 : 1유니 코드를 지원하지 않습니다. 2오류 메시지의 형식이 잘못되었습니다. 호출자가 반환 된 문자열을 처리해야하는 경우 처리 할 수 ​​있습니다. 구현은 호출자에게 옵션을 제공하지 않습니다. 3예외를 사용하지만 적절한 예외 안전이 부족합니다. 경우에 std::string사업자는 버퍼에 의해 할당, 예외를 던져 FormatMessage유출된다. 호출자가 참조로 객체를 전달하는 대신 4단순히를 반환하지 않는 이유는 무엇 std::string입니까?
IInspectable

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 ++의 오랜 기회 누락 기록에 추가됩니다.
IInspectable

쓸모없는 전화에 대한 좋은 지적 GetLastError. 그러나 GetLastError여기에 전화를 걸거나 발신자가 전화를 걸도록하는 것에는 차이가 없습니다 . C ++ 및 wchar에 관하여 : 희망을 버리지 마십시오. Microsoft는 앱을 UTF-8 전용 으로 허용하기 시작했습니다 .
Chronial

"차이가 없습니다" -다음 콜 사이트를 고려하십시오 log_error("error", GetLastErrorAsString());. 또한 log_error첫 번째 인수는 유형 이라는 것을 고려하십시오 std::string. (보이지 않는) 변환 c'tor 호출 GetLastError은 호출 한 시점에서 의미있는 가치를 포착한다는 보장을 포기 했습니다.
IInspectable

std :: string 생성자가 Win32 함수를 호출한다고 생각하는 이유는 무엇입니까?
Chronial

1
mallocHeapAlloc프로세스 힙을 호출 합니다. 프로세스 힙은 확장 가능합니다. 확대의 필요가있을 경우, 궁극적으로 호출합니다 VirtualAlloc을 , 않습니다 호출 스레드의 마지막 오류 코드를 설정합니다. 이제는 요점이 완전히 없어졌습니다. C ++은 지뢰밭입니다. 이 구현은 인터페이스를 단순히 구현할 수 없다는 암시 적 보증을 제공함으로써 추가됩니다. 문제가 없다고 생각 되면 제안 된 솔루션의 정확성 을 쉽게 증명할 수 있어야 합니다. 행운을 빕니다.
IInspectable

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

MinGW32에서와 같이 어셈블리 gnu를 사용하는 예제 (다시 말해서 오류 코드가 유효하다고 가정하고 그렇지 않으면 -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 자로 제한합니다. 도움이되지 않습니다.
IInspectable

당신은 그렇습니다. 아무도 유니 코드 버전의 부족에 대해 아무도 알지 않기를 바랐습니다. gnu로 유니 코드 문자열을 정의하고 솔루션을 수정하는 방법을 확인합니다. 부정직에 대해 죄송합니다.
드미트리

확인 유니 코드 버전이 있습니다. 그리고 그것은 임의의 이유가 아닙니다. 모든 오류 메시지는 80 자 미만이거나 읽을 가치가 없으며 오류 코드는 오류 메시지보다 중요합니다. 80자를 초과하는 표준 오류 메시지가 없으므로 안전한 가정이며, 그렇지 않은 경우 메모리가 누출되지 않습니다.
Dmitry

3
"모든 오류 메시지는 80 자 미만이거나 읽을 가치가 없습니다 . " -틀 렸습니다. ERROR_LOCK_VIOLATION(33)에 대한 오류 메시지 는 "다른 프로세스가 파일의 일부를 잠 갔기 때문에 프로세스가 파일에 액세스 할 수 없습니다."입니다. 문제를 해결하고 진단 로그 파일에서 찾을 경우 분명히 80 자보다 길고 읽을 가치가 있습니다. 이 답변은 기존 답변보다 실질적인 가치를 추가하지 않습니다.
IInspectable

이것은 순진하지 않습니다. 그것은 덜 자주 실패합니다. 버퍼 크기에 관한 어셈블리 구현을 제외하고 (100자를위한 공간 만있는 경우 200자를위한 공간이 있다고 주장). 다시 말하지만, 이것은 이미 다른 답변에없는 실질적인 것을 추가하지 않습니다. 특히, 이 답변 보다 나쁩니다 . 글 머리 기호 목록을 확인하고 방금 지적한 것 외에도 제안 된 구현에 어떤 문제가 있는지 관찰하십시오.
IInspectable
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.