Làm cách nào để sử dụng FormatMessage () đúng cách trong C ++?


90

Không có :

  • MFC
  • ATL

Tôi có thể sử dụng cách nào FormatMessage()để lấy văn bản lỗi cho 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);
 }

Câu trả lời:


134

Đây là cách thích hợp để lấy lại thông báo lỗi từ hệ thống cho một HRESULT(được đặt tên là hresult trong trường hợp này hoặc bạn có thể thay thế nó bằng 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;
}

Sự khác biệt chính giữa câu trả lời này và câu trả lời của David Hanak là việc sử dụng FORMAT_MESSAGE_IGNORE_INSERTSlá cờ. MSDN hơi không rõ ràng về cách sử dụng các chèn, nhưng Raymond Chen lưu ý rằng bạn không bao giờ nên sử dụng chúng khi truy xuất thông báo hệ thống, vì bạn không thể biết hệ thống mong đợi những chèn nào.

FWIW, nếu bạn đang sử dụng Visual C ++, bạn có thể làm cho cuộc sống của mình dễ dàng hơn một chút bằng cách sử dụng _com_errorlớp:

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

   //automatic cleanup when error goes out of scope
}

Theo như tôi biết thì không phải là một phần của MFC hoặc ATL trực tiếp.


8
Lưu ý: mã này sử dụng hResult thay cho mã lỗi Win32: đó là những điều khác nhau! Bạn có thể nhận được văn bản của một lỗi hoàn toàn khác với lỗi đã thực sự xảy ra.
Andrei Belogortseff

1
Điểm tuyệt vời, @Andrei - và thực sự, ngay cả khi lỗi lỗi Win32, quy trình này sẽ chỉ thành công nếu đó là lỗi hệ thống - một cơ chế xử lý lỗi mạnh mẽ sẽ cần phải biết nguồn gốc của lỗi, kiểm tra mã trước khi gọi FormatMessage và có thể truy vấn các nguồn khác thay thế.
Shog9

1
@AndreiBelogortseff Làm thế nào tôi có thể biết những gì cần sử dụng trong từng trường hợp? Ví dụ, RegCreateKeyExtrả về a LONG. Tài liệu của nó cho biết tôi có thể sử dụng FormatMessageđể lấy lỗi, nhưng tôi phải chuyển nó LONGthành một HRESULT.
csl

FormatMessage () nhận DWORD, @csl, một số nguyên không dấu được giả định là mã lỗi hợp lệ. Không phải tất cả các giá trị trả về - hoặc HRESULTS cho vấn đề đó - sẽ là mã lỗi hợp lệ; hệ thống giả định rằng bạn đã xác minh rằng đó là trước khi gọi hàm. Các tài liệu cho RegCreateKeyEx phải chỉ định khi nào giá trị trả về có thể được hiểu là một lỗi ... Thực hiện kiểm tra đó trước tiên và chỉ sau đó gọi FormatMessage.
Shog9

1
MSDN thực sự bây giờ cung cấp phiên bản của họ cùng một mã.
ahmd0

14

Hãy nhớ rằng bạn không thể làm những việc sau:

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

   // do something with the error...

   //automatic cleanup when error goes out of scope
}

Khi lớp được tạo và hủy trên ngăn xếp để lại errorText trỏ đến một vị trí không hợp lệ. Trong hầu hết các trường hợp, vị trí này vẫn sẽ chứa chuỗi lỗi, nhưng khả năng đó sẽ giảm nhanh khi viết các ứng dụng theo luồng.

Vì vậy, hãy luôn làm điều đó như sau đã được Shog9 trả lời ở trên:

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

   // do something with the error...

   //automatic cleanup when error goes out of scope
}

7
Đối _com_errortượng được tạo trên ngăn xếp trong cả hai ví dụ của bạn. Thuật ngữ bạn đang tìm là tạm thời . Trong ví dụ trước đây, đối tượng là đối tượng tạm thời bị hủy ở cuối câu lệnh.
Rob Kennedy

Đúng, có nghĩa là vậy. Nhưng tôi hy vọng hầu hết mọi người ít nhất có thể tìm ra điều đó từ mã. Về mặt kỹ thuật, thời gian tạm thời không bị hủy ở cuối câu lệnh, mà ở cuối điểm trình tự. (đó là điều tương tự trong ví dụ này vì vậy đây chỉ là những sợi tóc đang tách ra.)
Marius

1
Nếu bạn muốn làm cho nó an toàn (có thể không phải là rất hiệu quả ), bạn có thể làm điều này trong C ++:std::wstring strErrorText = _com_error(hresult).ErrorMessage();
ahmd0

11

Thử đi:

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
Chắc chắn bạn có thể tự mình thực hiện những điều chỉnh này.
oefe

@Atklin: Nếu bạn muốn sử dụng hresult từ một tham số, bạn rõ ràng không cần dòng đầu tiên (GetLastError ()).
David Hanak

4
GetLastError không trả về HResult. Nó trả về mã lỗi Win32. Có thể thích tên PrintLastError vì điều này không thực sự xử lý bất cứ điều gì. Và hãy chắc chắn sử dụng FORMAT_MESSAGE_IGNORE_INSERTS.
Rob Kennedy

Thanks for guys giúp đỡ của bạn :) - nhiều đánh giá cao
Aaron

5

Đây là phần bổ sung cho phần lớn các câu trả lời, nhưng thay vì sử LocalFree(errorText)dụng, hãy sử dụng HeapFreehàm:

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

Từ trang MSDN :

Windows 10 :
LocalFree không có trong SDK hiện đại, vì vậy nó không thể được sử dụng để giải phóng bộ đệm kết quả. Thay vào đó, hãy sử dụng HeapFree (GetProcessHeap (), ManagedMessage). Trong trường hợp này, điều này cũng giống như gọi LocalFree trên bộ nhớ.

Bản cập nhật
tôi tìm thấy LocalFreenằm trong phiên bản 10.0.10240.0 của SDK (dòng 1108 trong WinBase.h). Tuy nhiên, cảnh báo vẫn tồn tại trong liên kết trên.

#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

Cập nhật 2
Tôi cũng sẽ đề xuất sử dụng FORMAT_MESSAGE_MAX_WIDTH_MASKcờ để sắp xếp ngắt dòng trong thông báo hệ thống.

Từ trang MSDN :

FORMAT_MESSAGE_MAX_WIDTH_MASK
Hàm bỏ qua ngắt dòng thông thường trong văn bản định nghĩa thông báo. Hàm lưu trữ các ngắt dòng được mã hóa cứng trong văn bản định nghĩa thông báo vào bộ đệm đầu ra. Hàm không tạo ngắt dòng mới.

Cập nhật 3
Dường như có 2 mã lỗi hệ thống cụ thể không trả lại thông báo đầy đủ bằng cách sử dụng phương pháp được khuyến nghị:

Tại sao FormatMessage chỉ tạo một phần thông báo cho lỗi hệ thống ERROR_SYSTEM_PROCESS_TERMINATED và ERROR_UNHANDLED_EXCEPTION?


4

Đây là phiên bản của hàm David xử lý 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
Lưu ý rằng bạn không chuyển đúng kích thước bộ đệm _sntprintf_strong trường hợp UNICODE. Hàm lấy số ký tự, vì vậy bạn muốn _countofhoặc ARRAYSIZEaka sizeof(buffer) / sizeof(buffer[0])thay vì sizeof.
ThFabba

4

Kể từ c ++ 11, bạn có thể sử dụng thư viện chuẩn thay vì FormatMessage:

#include <system_error>

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

2

Như đã chỉ ra trong các câu trả lời khác:

  • FormatMessagenhận DWORDkết quả không phải là HRESULT(thường GetLastError()).
  • LocalFree cần thiết để giải phóng bộ nhớ được cấp phát bởi FormatMessage

Tôi đã lấy những điểm trên và thêm một vài điểm nữa cho câu trả lời của mình:

  • Gói FormatMessagetrong một lớp để cấp phát và giải phóng bộ nhớ khi cần thiết
  • Sử dụng quá tải toán tử (ví dụ: operator LPTSTR() const { return ...; }để lớp của bạn có thể được sử dụng như một chuỗi
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;

};

Tìm phiên bản hoàn chỉnh hơn của đoạn mã trên tại đây: https://github.com/stephenquan/FormatMessage

Với lớp trên, cách sử dụng đơn giản là:

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

0

Đoạn mã dưới đây là mã tương đương C ++ mà tôi đã viết ra trái ngược với ErrorExit () của Microsoft nhưng được thay đổi một chút để tránh tất cả các macro và sử dụng unicode. Ý tưởng ở đây là tránh phôi và mallocs không cần thiết. Tôi không thể thoát khỏi tất cả các phôi C nhưng đây là điều tốt nhất tôi có thể tập hợp. Liên quan đến FormatMessageW (), yêu cầu một con trỏ được cấp phát bởi hàm định dạng và Id lỗi từ GetLastError (). Con trỏ sau static_cast có thể được sử dụng như một con trỏ wchar_t bình thường.

#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);
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.