C ++에서 FormatMessage ()를 올바르게 사용하려면 어떻게해야합니까?


90

없이 :

  • MFC
  • ATL

FormatMessage()대한 오류 텍스트를 얻는 데 어떻게 사용할 수 HRESULT있습니까?

 HRESULT hresult = application.CreateInstance("Excel.Application");

 if (FAILED(hresult))
 {
     // what should i put here to obtain a human-readable
     // description of the error?
     exit (hresult);
 }

답변:


134

다음은 시스템에서 오류 메시지를 다시 가져 오는 적절한 방법입니다 HRESULT(이 경우 hresult로 명명하거나으로 바꿀 수 있음 GetLastError()).

LPTSTR errorText = NULL;

FormatMessage(
   // use system message tables to retrieve error text
   FORMAT_MESSAGE_FROM_SYSTEM
   // allocate buffer on local heap for error text
   |FORMAT_MESSAGE_ALLOCATE_BUFFER
   // Important! will fail otherwise, since we're not 
   // (and CANNOT) pass insertion parameters
   |FORMAT_MESSAGE_IGNORE_INSERTS,  
   NULL,    // unused with FORMAT_MESSAGE_FROM_SYSTEM
   hresult,
   MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
   (LPTSTR)&errorText,  // output 
   0, // minimum size for output buffer
   NULL);   // arguments - see note 
   
if ( NULL != errorText )
{
   // ... do something with the string `errorText` - log it, display it to the user, etc.

   // release memory allocated by FormatMessage()
   LocalFree(errorText);
   errorText = NULL;
}

이것과 David Hanak의 대답의 주요 차이점은 FORMAT_MESSAGE_IGNORE_INSERTS깃발 사용입니다 . MSDN은 삽입을 사용하는 방법에 대해 약간 불분명하지만 Raymond Chen 은 시스템 메시지를 검색 때 삽입을 사용해서는 안된다고 지적합니다 . 시스템이 어떤 삽입을 예상하는지 알 수있는 방법이 없기 때문입니다.

FWIW, Visual C ++를 사용하는 경우 _com_error클래스 를 사용하여 생활을 좀 더 쉽게 만들 수 있습니다 .

{
   _com_error error(hresult);
   LPCTSTR errorText = error.ErrorMessage();
   
   // do something with the error...

   //automatic cleanup when error goes out of scope
}

내가 아는 한 MFC 또는 ATL의 일부가 아닙니다.


8
주의 :이 코드는 Win32 오류 코드 대신 hResult를 사용합니다. 실제로 발생한 오류와 완전히 다른 오류의 텍스트를 얻을 수 있습니다.
Andrei Belogortseff 2014 년

1
@Andrei입니다. 실제로 오류 Win32 오류 인 경우에도이 루틴은 시스템 오류 인 경우에만 성공합니다 . 강력한 오류 처리 메커니즘은 오류의 원인을 인식하고 코드를 검사해야합니다. FormatMessage를 호출하기 전에 다른 소스를 대신 쿼리하십시오.
Shog9 2014 년

1
@AndreiBelogortseff 각 경우에 무엇을 사용해야하는지 어떻게 알 수 있습니까? 예를 들어, RegCreateKeyEx반환합니다 LONG. 그 문서는 내가 사용하는 수 있다고 FormatMessage오류를 검색 할 수 있지만이 캐스팅해야 LONGHRESULT.
csl

FormatMessage ()는 유효한 오류 코드로 간주 되는 부호없는 정수인 DWORD, @csl 을 사용합니다. 모든 반환 값 또는 HRESULTS가 유효한 오류 코드는 아닙니다. 시스템은 함수를 호출하기 전에 확인했다고 가정합니다. RegCreateKeyEx에 대한 문서는 반환 값이 오류로 해석 될 수있는시기를 지정해야합니다 ... 먼저 확인을 수행 한 다음 FormatMessage를 호출합니다.
Shog9

1
MSDN은 실제로 동일한 코드 의 버전 을 제공 합니다.
ahmd0

14

다음은 수행 할 수 없습니다.

{
   LPCTSTR errorText = _com_error(hresult).ErrorMessage();

   // do something with the error...

   //automatic cleanup when error goes out of scope
}

클래스가 스택에서 생성되고 소멸됨에 따라 errorText는 잘못된 위치를 가리 킵니다. 대부분의 경우이 위치에는 여전히 오류 문자열이 포함되지만 스레드 응용 프로그램을 작성할 때 그 가능성이 빠르게 사라집니다.

따라서 항상 위의 Shog9의 답변대로 다음과 같이하십시오.

{
   _com_error error(hresult);
   LPCTSTR errorText = error.ErrorMessage();

   // do something with the error...

   //automatic cleanup when error goes out of scope
}

7
_com_error객체의 스택에 생성됩니다 모두 당신의 예. 찾고있는 용어는 일시적 입니다. 앞의 예에서 오브젝트는 명령문 끝에서 소멸되는 임시입니다.
Rob Kennedy

네, 그 뜻입니다. 그러나 나는 대부분의 사람들이 적어도 코드에서 그것을 알아낼 수 있기를 바랍니다. 기술적으로 임시는 명령문의 끝이 아니라 시퀀스 지점의 끝에서 삭제됩니다. (이 예에서 동일한 것이므로 머리카락을 쪼개는 것입니다.)
Marius

1
안전하게 만들고 싶다면 (매우 효율적 이지 않을 수도 있음 ) C ++에서 다음과 같이 할 수 있습니다.std::wstring strErrorText = _com_error(hresult).ErrorMessage();
ahmd0

11

이 시도:

void PrintLastError (const char *msg /* = "Error occurred" */) {
        DWORD errCode = GetLastError();
        char *err;
        if (!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
                           NULL,
                           errCode,
                           MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // default language
                           (LPTSTR) &err,
                           0,
                           NULL))
            return;

        static char buffer[1024];
        _snprintf(buffer, sizeof(buffer), "ERROR: %s: %s\n", msg, err);
        OutputDebugString(buffer); // or otherwise log it
        LocalFree(err);
}

void HandleLastError (hresult)?
Aaron

1
확실히 당신은 이러한 적응을 스스로 할 수 있습니다.
oefe

@Atklin : 매개 변수에서 hresult를 사용하려면 분명히 첫 번째 줄 (GetLastError ())이 필요하지 않습니다.
David Hanak

4
GetLastError는 HResult를 반환하지 않습니다. Win32 오류 코드를 반환합니다. 실제로 아무것도 처리 하지 않으므로 PrintLastError라는 이름을 선호 할 수 있습니다. FORMAT_MESSAGE_IGNORE_INSERTS를 사용해야합니다.
Rob Kennedy

:) 당신의 도움들 주셔서 감사합니다 - 많은 감사
아론

5

이것은 대부분의 답변에 더 많은 추가 사항이지만 함수 를 LocalFree(errorText)사용하는 대신 다음을 사용하십시오 HeapFree.

::HeapFree(::GetProcessHeap(), NULL, errorText);

MSDN 사이트에서 :

Windows 10 :
LocalFree는 최신 SDK에 없으므로 결과 버퍼를 해제하는 데 사용할 수 없습니다. 대신 HeapFree (GetProcessHeap (), assignedMessage)를 사용하십시오. 이 경우 메모리에서 LocalFree를 호출하는 것과 동일합니다.

업데이트 버전 10.0.10240.0 SDK (WinBase.h의 1108 행)에
있음을 발견했습니다 LocalFree. 그러나 위의 링크에는 경고가 여전히 존재합니다.

#pragma region Desktop Family or OneCore Family
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM)

WINBASEAPI
_Success_(return==0)
_Ret_maybenull_
HLOCAL
WINAPI
LocalFree(
    _Frees_ptr_opt_ HLOCAL hMem
    );

#endif /* WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM) */
#pragma endregion

업데이트 2
또한 FORMAT_MESSAGE_MAX_WIDTH_MASK플래그를 사용하여 시스템 메시지의 줄 바꿈을 정리하는 것이 좋습니다 .

MSDN 사이트에서 :

FORMAT_MESSAGE_MAX_WIDTH_MASK
이 함수는 메시지 정의 텍스트에서 일반 줄 바꿈을 무시합니다. 이 함수는 메시지 정의 텍스트의 하드 코딩 된 줄 바꿈을 출력 버퍼에 저장합니다. 이 함수는 새 줄 바꿈을 생성하지 않습니다.

업데이트 3
권장 방법을 사용하여 전체 메시지를 반환하지 않는 두 가지 특정 시스템 오류 코드가있는 것 같습니다.

FormatMessage가 ERROR_SYSTEM_PROCESS_TERMINATED 및 ERROR_UNHANDLED_EXCEPTION 시스템 오류에 대한 부분 메시지 만 만드는 이유는 무엇입니까?


4

다음은 유니 코드를 처리하는 David의 함수 버전입니다.

void HandleLastError(const TCHAR *msg /* = "Error occured" */) {
    DWORD errCode = GetLastError();
    TCHAR *err;
    if (!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
                       NULL,
                       errCode,
                       MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // default language
                       (LPTSTR) &err,
                       0,
                       NULL))
        return;

    //TRACE("ERROR: %s: %s", msg, err);
    TCHAR buffer[1024];
    _sntprintf_s(buffer, sizeof(buffer), _T("ERROR: %s: %s\n"), msg, err);
    OutputDebugString(buffer);
    LocalFree(err);

}


1
_sntprintf_sUNICODE 경우에 올바른 버퍼 크기를 전달하지 않습니다 . 당신이 원하는 있도록 기능, 문자의 수를 취 _countof하거나 ARRAYSIZE일명 sizeof(buffer) / sizeof(buffer[0])대신의 sizeof.
ThFabba

4

C ++ 11부터 다음 대신 표준 라이브러리를 사용할 수 있습니다 FormatMessage.

#include <system_error>

std::string message = std::system_category().message(hr)

2

다른 답변에서 지적했듯이 :

  • FormatMessage걸리는 DWORD결과가 아닌 HRESULT(대개을 GetLastError()).
  • LocalFree 할당 된 메모리를 해제하는 데 필요합니다. FormatMessage

나는 위의 요점을 취하고 내 대답을 위해 몇 가지를 더 추가했습니다.

  • FormatMessage필요에 따라 메모리를 할당하고 해제하기 위해 클래스를 래핑합니다 .
  • 연산자 오버로드를 사용하십시오 (예 : operator LPTSTR() const { return ...; }클래스를 문자열로 사용할 수 있도록
class CFormatMessage
{
public:
    CFormatMessage(DWORD dwMessageId,
                   DWORD dwLanguageId = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT)) :
        m_text(NULL)
    {
        Assign(dwMessageId, dwLanguageId);
    }

    ~CFormatMessage()
    {
        Clear();
    }

    void Clear()
    {
        if (m_text)
        {
            LocalFree(m_text);
            m_text = NULL;
        }
    }

    void Assign(DWORD dwMessageId,
                DWORD dwLanguageId = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT))
    {
        Clear();
        DWORD dwFlags = FORMAT_MESSAGE_FROM_SYSTEM
            | FORMAT_MESSAGE_ALLOCATE_BUFFER
            | FORMAT_MESSAGE_IGNORE_INSERTS,
        FormatMessage(
            dwFlags,
            NULL,
            dwMessageId,
            dwLanguageId,
            (LPTSTR) &m_text,
            0,
            NULL);
    }

    LPTSTR text() const { return m_text; }
    operator LPTSTR() const { return text(); }

protected:
    LPTSTR m_text;

};

https://github.com/stephenquan/FormatMessage 에서 위 코드의 더 완전한 버전을 찾으십시오.

위의 클래스에서 사용법은 간단합니다.

    std::wcout << (LPTSTR) CFormatMessage(GetLastError()) << L"\n";

0

아래 코드는 Microsoft의 ErrorExit () 와 달리 작성한 C ++에 해당하는 코드 이지만 모든 매크로를 피하고 유니 코드를 사용하도록 약간 변경되었습니다. 여기서 아이디어는 불필요한 캐스트와 malloc을 피하는 것입니다. 나는 모든 C 캐스트를 벗어날 수 없었지만 이것이 내가 소집 할 수있는 최선의 방법이다. FormatMessageW ()와 관련된 것으로, 형식 함수에 의해 할당되는 포인터와 GetLastError ()의 오류 ID가 필요합니다. static_cast 이후의 포인터는 일반적인 wchar_t 포인터처럼 사용할 수 있습니다.

#include <string>
#include <windows.h>

void __declspec(noreturn) error_exit(const std::wstring FunctionName)
{
    // Retrieve the system error message for the last-error code
    const DWORD ERROR_ID = GetLastError();
    void* MsgBuffer = nullptr;
    LCID lcid;
    GetLocaleInfoEx(L"en-US", LOCALE_RETURN_NUMBER | LOCALE_ILANGUAGE, (wchar_t*)&lcid, sizeof(lcid));

    //get error message and attach it to Msgbuffer
    FormatMessageW(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL, ERROR_ID, lcid, (wchar_t*)&MsgBuffer, 0, NULL);
    //concatonate string to DisplayBuffer
    const std::wstring DisplayBuffer = FunctionName + L" failed with error " + std::to_wstring(ERROR_ID) + L": " + static_cast<wchar_t*>(MsgBuffer);

    // Display the error message and exit the process
    MessageBoxExW(NULL, DisplayBuffer.c_str(), L"Error", MB_ICONERROR | MB_OK, static_cast<WORD>(lcid));

    ExitProcess(ERROR_ID);
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.