วิธีรับข้อความแสดงข้อผิดพลาดจากรหัสข้อผิดพลาดที่ GetLastError () ส่งคืน


144

หลังจากการเรียกใช้ Windows API ฉันจะรับข้อความแสดงข้อผิดพลาดล่าสุดในรูปแบบข้อความได้อย่างไร

GetLastError() ส่งคืนค่าจำนวนเต็มไม่ใช่ข้อความ


ใช้เพื่อค้นหาข้อผิดพลาด exe ในส่วนเครื่องมือในสตูดิโอภาพซึ่งทำได้ดีเมื่อคุณต้องการข้อความจากข้อผิดพลาดสำหรับการดีบักเท่านั้น
ColdCat

1
@ColdCat: สำหรับการดีบักมันง่ายกว่ามากที่จะเพิ่ม@err,hrนาฬิกาและให้ดีบักเกอร์แปลงรหัสข้อผิดพลาดสุดท้ายโดยอัตโนมัติเป็นการแสดงที่มนุษย์อ่านได้ ตัว,hrระบุรูปแบบทำงานสำหรับนิพจน์ใด ๆ ที่ประเมินเป็นค่าเชิงหนึ่งเช่น5,hrนาฬิกาจะแสดง"ERROR_ACCESS_DENIED: การเข้าถึงถูกปฏิเสธ" .
IInspectable

2
จากGetLastError()เอกสาร: " ในการรับสตริงข้อผิดพลาดสำหรับรหัสข้อผิดพลาดของระบบให้ใช้FormatMessage()ฟังก์ชัน " ดูตัวอย่างการดึง Last-Error Codeบน MSDN
Remy Lebeau

คำตอบ:


151
//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
โอ้ว้าวใช่มันแปลกมาก มันจะปรับเปลี่ยนตัวชี้อย่างไร? แต่ส่งที่อยู่ของตัวชี้ (ตัวชี้ไปยังตัวชี้) แต่ส่งไปยังตัวชี้ปกติ ... ขอบคุณสำหรับการแจ้งเตือนแก้ไขในฐานรหัสของฉันเอง (และคำตอบของฉัน) จับได้ละเอียดมาก
Jamin Grey

1
ขอบคุณมากตัวอย่างของคุณชัดเจนกว่าตัวอย่างจาก MSDN มาก ยิ่งไปกว่านั้นหนึ่งจาก MSDN ไม่สามารถรวบรวมได้ มันมีส่วนstrsafe.hหัวบางส่วนที่ไม่ปลอดภัยเลยทำให้เกิดข้อผิดพลาดของคอมไพเลอร์ในwinuser.hและwinbase.h.
Hi-Angel

2
ไม่รองรับรหัสข้อผิดพลาดบางอย่าง ตัวอย่างเช่น 0x2EE7 ERROR_INTERNET_NAME_NOT_RESOLVED ทำให้เกิดข้อผิดพลาดใหม่เมื่อเรียก FormatMessage: 0x13D ระบบไม่พบข้อความสำหรับข้อความหมายเลข 0x% 1 ในไฟล์ข้อความสำหรับ% 2
Brent

3
ปัญหาเกี่ยวกับการใช้งานนี้: 1 GetLastErrorอาจเรียกได้ว่าสายเกินไป 2ไม่มีการสนับสนุน Unicode 3การใช้ข้อยกเว้นโดยไม่ต้องใช้การรับประกันความปลอดภัยข้อยกเว้น
IInspectable

66

อัปเดต (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ไม่มีการจัดการข้อผิดพลาด
IInspectable

1
sizeof (buf) ควรเป็น ARRAYSIZE (buf) เนื่องจาก FormatMessage คาดว่าขนาดของบัฟเฟอร์ใน TCHAR ไม่ใช่เป็นไบต์
ไก่

1
เราไม่สามารถใช้256แทนได้(sizeof(buf) / sizeof(wchar_t)หรือไม่? เป็นที่ยอมรับและปลอดภัยหรือไม่?
BattleTested


17

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สำหรับข้อมูลเพิ่มเติม
  • การจัดการข้อผิดพลาดที่เหมาะสม / การรายงานข้อผิดพลาดซึ่งแตกต่างจากคำตอบอื่น ๆ บางส่วนที่เพิกเฉยต่อข้อผิดพลาด


คำตอบนี้รวมอยู่ในเอกสาร Stack Overflow ผู้ใช้ต่อไปมีส่วนร่วมในตัวอย่าง: stackptr , Ajay , โคดี้สีเทา♦ , 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 คืออะไร? ฟังก์ชั่นลดความซับซ้อนโดยการจัดเตรียม abstractions ในกรณีนี้สิ่งที่เป็นนามธรรมจะลดจำนวนพารามิเตอร์จาก 7 เป็น 3 ดำเนินการตามเอกสารสัญญาของ API โดยส่งแฟล็กและพารามิเตอร์ที่เข้ากันได้และเข้ารหัสตรรกะการรายงานข้อผิดพลาดที่เป็นเอกสาร มันมีอินเตอร์เฟซที่กระชับกับเรื่องง่ายที่จะคาดเดาความหมายประหยัดที่คุณจำเป็นต้องลงทุน 11 นาทีในการอ่านเอกสาร
IInspectable

ฉันชอบคำตอบนี้ชัดเจนมากว่าได้ใส่ใจอย่างรอบคอบเพื่อความถูกต้องของรหัส ฉันมีสองคำถาม 1) เหตุใดคุณจึงใช้::HeapFree(::GetProcessHeap(), 0, p)ใน deleter แทน::LocalFree(p)ตามที่เอกสารแนะนำ 2) ฉันตระหนักดีว่าเอกสารระบุว่าให้ทำเช่นนี้ แต่ไม่ได้reinterpret_cast<LPTSTR>(&psz)ละเมิดกฎการใช้นามแฝงที่เข้มงวด?
Teh JoE

@teh: ขอบคุณสำหรับคำติชม คำถาม 1): พูดตามตรงฉันไม่รู้ว่ามันปลอดภัยหรือเปล่า ฉันจำไม่ได้ว่าใครหรือทำไมโทรไปHeapFreeแนะนำให้รู้จักในขณะที่ยังคงเป็นหัวข้อใน SO Documentation ฉันจะต้องตรวจสอบ (แม้ว่าเอกสารดูเหมือนจะชัดเจน แต่ก็ไม่ปลอดภัย) คำถาม 2): นี่คือสองเท่า สำหรับการกำหนดค่า MBCS LPTSTRเป็นนามแฝงสำหรับchar*. นักแสดงนั้นปลอดภัยเสมอ สำหรับการกำหนดค่า Unicode การแคสต์ให้wchar_t*มีความคาวในทุกอัตรา ฉันไม่รู้ว่าสิ่งนี้ปลอดภัยใน C หรือไม่ แต่ส่วนใหญ่จะไม่อยู่ใน C ++ ฉันต้องตรวจสอบเรื่องนี้ด้วย
IInspectable


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

8

หากคุณใช้ 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 ++ ? ตามที่กล่าวมาคำตอบนี้ไม่ได้ตอบคำถามที่ถูกถามด้วยซ้ำ
IInspectable

1
ฉันจำไม่ได้ว่าเคยโหวตให้กับคำตอบที่เสนอนี้ ฉันพูดถึงข้อบกพร่องที่ชัดเจนในตรรกะของ @swdev แต่เนื่องจากคุณจะไม่เชื่อฉันตอนนี้ฉันจะพิสูจน์ให้คุณเห็น: ที่นี่มีการลงคะแนนอีกครั้ง อันนั้นมาจากฉันเพราะคำตอบนี้ - ในขณะที่คำถามอื่นอาจมีประโยชน์ แต่ก็ไม่มีประโยชน์สำหรับคำถามที่ถูกถาม
IInspectable

2
"ผมคิดว่าคุณมีข้อมูลเชิงลึกที่เป็นประโยชน์ที่จะนำเสนอนอกเหนือจากที่เห็นได้ชัด" - อันที่จริงฉันมี
IInspectable

5

ตั้งแต่ 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: อย่าละทิ้งความหวังไมโครซอฟท์เริ่มที่จะอนุญาตให้แอปเป็น UTF-8 เท่านั้น
โครเนียล

"ผมเห็นไม่แตกต่างกัน" - log_error("error", GetLastErrorAsString());พิจารณาเว็บไซต์โทรต่อไปนี้: นอกจากนี้ยังพิจารณาว่าlog_error's std::stringอาร์กิวเมนต์แรกเป็นประเภท Conversion (ที่มองไม่เห็น) หรือการโทรเพียงแค่ยกเลิกการค้ำประกันของคุณในการจับมูลค่าที่มีความหมายจากGetLastErrorจุดที่คุณเรียกมัน
IInspectable

ทำไมคุณถึงคิดว่า std :: string constructor เรียกใช้ฟังก์ชัน Win32
โครเนียล

1
mallocเรียกร้องHeapAllocให้ฮีปกระบวนการ ฮีปกระบวนการสามารถเติบโตได้ หากจะต้องมีการเติบโตในท้ายที่สุดมันจะเรียกVirtualAllocซึ่งไม่ตั้งรหัสข้อผิดพลาดของเธรดการโทรล่าสุด ตอนนี้ขาดประเด็นไปโดยสิ้นเชิงซึ่งก็คือ C ++ เป็นที่วางทุ่นระเบิด การใช้งานนี้เพิ่มเข้าไปโดยการให้อินเทอร์เฟซที่มีการรับประกันโดยนัยเพียงแค่ไม่สามารถอยู่ได้ หากคุณเชื่อว่าไม่มีปัญหาคุณควรพิสูจน์ความถูกต้องของโซลูชันที่เสนอได้โดยง่าย โชคดี.
IInspectable

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พ่นยกเว้นการดำเนินงานนี้การรั่วไหลของหน่วยความจำที่จัดสรรโดยการเรียกร้องให้
IInspectable

จริง แต่ฉันใช้รหัสนี้มาหลายปีแล้วและไม่เคยมีปัญหา กรณีเดียวที่ ctor CString มีแนวโน้มที่จะโยนคือความล้มเหลวในการจัดสรรหน่วยความจำและรหัส MFC ส่วนใหญ่รวมถึงสิ่งที่ Microsoft จัดเตรียมไว้ไม่ได้จัดการกับสภาวะหน่วยความจำที่ไม่เพียงพออย่างที่คุณต้องการ โชคดีที่ตอนนี้พีซีส่วนใหญ่มีหน่วยความจำมากมายจนคุณต้องทำงานอย่างหนักเพื่อที่จะใช้มันทั้งหมด การใช้งานใด ๆ ที่สร้างอินสแตนซ์ CString ชั่วคราว (รวมถึงการส่งคืน CString) จะเสี่ยงนี้ เป็นการแลกเปลี่ยนความเสี่ยง / ความสะดวกสบาย นอกจากนี้หากเกิดขึ้นในตัวจัดการข้อความข้อยกเว้นจะถูกจับได้
เหยื่อ

ความคิดเห็นนี้ส่วนใหญ่ผิดพลาดขออภัย "มันไม่เคยเกิดขึ้นกับฉัน"เป็นจุดอ่อนที่ต้องทำโดยเฉพาะอย่างยิ่งเมื่อคุณรู้ว่าโค้ดล้มเหลวได้อย่างไร จำนวนหน่วยความจำไม่มีผลกระทบต่อพื้นที่แอดเดรสที่มีอยู่ที่จัดสรรให้กับกระบวนการเช่นกัน RAM เป็นเพียงการเพิ่มประสิทธิภาพ Copy-elision ป้องกันการจัดสรรชั่วคราวเมื่อมีการเขียนโค้ดเพื่ออนุญาต 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ไม่มีการสนับสนุน Unicode 2การจัดรูปแบบข้อความแสดงข้อผิดพลาดไม่เหมาะสม หากผู้เรียกต้องการประมวลผลสตริงที่ส่งคืนก็สามารถทำได้ การใช้งานของคุณทำให้ผู้โทรไม่มีตัวเลือก 3การใช้ข้อยกเว้น แต่ขาดความปลอดภัยข้อยกเว้นที่เหมาะสม ในกรณีที่std::stringตัวดำเนินการทิ้งข้อยกเว้นบัฟเฟอร์ที่จัดสรรโดยFormatMessageจะรั่วไหล 4ทำไมไม่เพียงส่งคืน a std::stringแทนที่จะให้ผู้โทรส่งผ่านวัตถุโดยการอ้างอิง?
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;
}

ตัวอย่างที่ใช้กับแอสเซมบลี 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 อักขระอีกครั้งโดยไม่มีเหตุผลใด ๆ ฉันเกรงว่าจะไม่เป็นประโยชน์
IInspectable

คุณพูดถูกฉันหวังว่าจะไม่มีใครสังเกตเห็นเกี่ยวกับการขาดเวอร์ชัน Unicode ฉันจะตรวจสอบวิธีกำหนดสตริง Unicode ใน gnu เป็นและแก้ไขโซลูชันของฉัน ขออภัยเกี่ยวกับความไม่ซื่อสัตย์
Dmitry

ตกลงเวอร์ชัน Unicode ขึ้นแล้ว และไม่ใช่เหตุผลโดยพลการ ข้อความแสดงข้อผิดพลาดทั้งหมดมีอักขระน้อยกว่า 80 ตัวหรือไม่ควรอ่านและรหัสข้อผิดพลาดมีความสำคัญมากกว่าข้อความแสดงข้อผิดพลาด ไม่มีข้อความแสดงข้อผิดพลาดมาตรฐานที่มีอักขระเกิน 80 ตัวดังนั้นจึงเป็นข้อสันนิษฐานที่ปลอดภัยและเมื่อไม่เป็นเช่นนั้นก็จะไม่ทำให้หน่วยความจำรั่วไหล
Dmitry

3
"ข้อความแสดงข้อผิดพลาดทั้งหมดมีอักขระน้อยกว่า 80 ตัวหรือไม่ควรอ่าน" - ผิด ข้อความแสดงข้อผิดพลาดสำหรับERROR_LOCK_VIOLATION(33) คือ: "กระบวนการไม่สามารถเข้าถึงไฟล์ได้เนื่องจากกระบวนการอื่นได้ล็อกบางส่วนของไฟล์" มีทั้งความยาวมากกว่า 80 อักขระอย่างชัดเจนและควรค่าแก่การอ่านเป็นอย่างมากหากคุณกำลังพยายามแก้ไขปัญหาและพบสิ่งนี้ในไฟล์บันทึกการวินิจฉัย คำตอบนี้ไม่ได้เพิ่มมูลค่าที่สำคัญเหนือคำตอบที่มีอยู่
IInspectable

นี่เป็นเรื่องไร้เดียงสาไม่น้อย มันเป็นเพียงความล้มเหลวน้อยลง ยกเว้นการใช้งานแอสเซมบลีที่เกี่ยวกับขนาดบัฟเฟอร์ (อ้างว่ามีที่ว่างสำหรับ 200 อักขระเมื่อมีที่ว่างสำหรับ 100 เท่านั้น) อีกครั้งสิ่งนี้ไม่ได้เพิ่มอะไรเป็นชิ้นเป็นอันนั่นยังไม่ได้อยู่ในคำตอบอื่น ๆ โดยเฉพาะอย่างยิ่งมันแย่กว่าคำตอบนี้ ดูรายการสัญลักษณ์แสดงหัวข้อย่อยที่นั่นและสังเกตว่าประเด็นใดที่คุณนำเสนอไปใช้งานด้านบนของรายการที่ฉันเพิ่งชี้ไป
IInspectable
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.