ฉันจะใช้ FormatMessage () อย่างถูกต้องใน C ++ ได้อย่างไร


90

ไม่มี :

  • MFC
  • ATL

ฉันFormatMessage()จะใช้เพื่อรับข้อความแสดงข้อผิดพลาดสำหรับ a ได้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
ระวัง: รหัสนี้ใช้ hResult แทนรหัสข้อผิดพลาด Win32 นั่นคือสิ่งที่แตกต่างกัน! คุณอาจได้รับข้อความของข้อผิดพลาดที่แตกต่างไปจากที่เกิดขึ้นจริง
Andrei Belogortseff

1
จุดที่ยอดเยี่ยม @Andrei - และแม้ว่าข้อผิดพลาดจะเป็นข้อผิดพลาด Win32 รูทีนนี้จะสำเร็จก็ต่อเมื่อเป็นข้อผิดพลาดของระบบ - กลไกการจัดการข้อผิดพลาดที่มีประสิทธิภาพจะต้องตระหนักถึงแหล่งที่มาของข้อผิดพลาดตรวจสอบรหัส ก่อนที่จะเรียก FormatMessage และอาจสอบถามแหล่งที่มาอื่นแทน
Shog9

1
@AndreiBelogortseff ฉันจะรู้ได้อย่างไรว่าจะใช้อะไรในแต่ละกรณี? ตัวอย่างเช่นRegCreateKeyExส่งกลับไฟล์LONG. เอกสารบอกว่าฉันสามารถใช้FormatMessageเพื่อดึงข้อผิดพลาดได้ แต่ฉันต้องLONGแปลงเป็นHRESULTไฟล์.
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);
}

โมฆะ HandleLastError (hresult)?
แอรอน

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 (), protectedMessage) แทน ในกรณีนี้จะเหมือนกับการเรียก LocalFree บนหน่วยความจำ

การอัปเดต
ฉันพบว่าLocalFreeอยู่ในเวอร์ชัน 10.0.10240.0 ของ SDK (บรรทัด 1108 ใน WinBase.h) อย่างไรก็ตามคำเตือนยังคงมีอยู่ในลิงก์ด้านบน

#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
ดูเหมือนจะมีรหัสข้อผิดพลาดของระบบ 2 รหัสที่ไม่ส่งคืนข้อความทั้งหมดโดยใช้แนวทางที่แนะนำ:

เหตุใด FormatMessage จึงสร้างเฉพาะข้อความบางส่วนสำหรับ ERROR_SYSTEM_PROCESS_TERMINATED และ ERROR_UNHANDLED_EXCEPTION ข้อผิดพลาดของระบบ



4

นี่คือเวอร์ชันของฟังก์ชันของ David ที่จัดการกับ Unicode

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_sในกรณี UNICODE ฟังก์ชันรับจำนวนอักขระดังนั้นคุณจึงต้องการ_countofหรือARRAYSIZEaka sizeof(buffer) / sizeof(buffer[0])แทนsizeof.
ThFabba

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

โค้ดด้านล่างนี้เป็นโค้ดเทียบเท่า C ++ ที่ฉันเขียนไว้ตรงกันข้ามกับErrorExit () ของ Microsoftแต่มีการเปลี่ยนแปลงเล็กน้อยเพื่อหลีกเลี่ยงมาโครทั้งหมดและใช้ Unicode แนวคิดในที่นี้คือการหลีกเลี่ยงการร่ายและ mallocs ที่ไม่จำเป็น ฉันไม่สามารถหลบหนีการร่าย C ได้ทั้งหมด แต่นี่เป็นสิ่งที่ดีที่สุดที่ฉันสามารถรวบรวมได้ เกี่ยวข้องกับ FormatMessageW () ซึ่งต้องใช้ตัวชี้เพื่อจัดสรรโดยฟังก์ชันรูปแบบและรหัสข้อผิดพลาดจาก GetLastError () ตัวชี้หลัง 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.