Bagaimana saya harus menggunakan FormatMessage () dengan benar di C ++?

90

Tanpa :

  • MFC
  • ATL

Bagaimana saya bisa menggunakan FormatMessage()untuk mendapatkan teks kesalahan untuk 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);
 }
Aaron
sumber

Jawaban:

134

Berikut cara yang tepat untuk mendapatkan pesan kesalahan kembali dari sistem untuk HRESULT(dalam kasus ini dinamai hresult, atau Anda dapat menggantinya dengan 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;
}

Perbedaan utama antara ini dan jawaban David Hanak adalah penggunaan FORMAT_MESSAGE_IGNORE_INSERTSbendera. MSDN agak tidak jelas tentang bagaimana penyisipan harus digunakan, tetapi Raymond Chen mencatat bahwa Anda tidak boleh menggunakannya saat mengambil pesan sistem, karena Anda tidak memiliki cara untuk mengetahui penyisipan mana yang diharapkan sistem.

FWIW, jika Anda menggunakan Visual C ++ Anda dapat membuat hidup Anda sedikit lebih mudah dengan menggunakan _com_errorkelas:

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

   //automatic cleanup when error goes out of scope
}

Bukan bagian dari MFC atau ATL secara langsung sejauh yang saya ketahui.

Shog9
sumber
8
Hati-hati: kode ini menggunakan hResult sebagai pengganti kode kesalahan Win32: itu adalah hal yang berbeda! Anda mungkin mendapatkan teks kesalahan yang sama sekali berbeda dari yang sebenarnya terjadi.
Andrei Belogortseff
1
Poin yang sangat baik, @Andrei - dan memang, meskipun kesalahannya adalah kesalahan Win32, rutinitas ini hanya akan berhasil jika itu adalah kesalahan sistem - mekanisme penanganan kesalahan yang kuat perlu mengetahui sumber kesalahan, periksa kodenya sebelum memanggil FormatMessage dan mungkin menanyakan sumber lain sebagai gantinya.
Shog9
1
@AndreiBelogortseff Bagaimana saya tahu apa yang harus digunakan dalam setiap kasus? Misalnya, RegCreateKeyExmengembalikan a LONG. Dokumennya mengatakan saya dapat menggunakan FormatMessageuntuk mengambil kesalahan, tetapi saya harus memasukkannya LONGke file HRESULT.
csl
FormatMessage () menggunakan DWORD, @csl, bilangan bulat unsigned yang dianggap sebagai kode kesalahan yang valid. Tidak semua nilai yang dikembalikan - atau HRESULTS dalam hal ini - akan menjadi kode kesalahan yang valid; sistem menganggap Anda telah memverifikasi bahwa itu sebelum memanggil fungsi. Dokumen untuk RegCreateKeyEx harus menentukan kapan nilai yang dikembalikan dapat ditafsirkan sebagai kesalahan ... Lakukan pemeriksaan itu terlebih dahulu , dan baru kemudian panggil FormatMessage.
Shog9
1
MSDN sebenarnya sekarang menyediakan versi kode yang agak sama.
ahmd0
14

Ingatlah bahwa Anda tidak dapat melakukan hal berikut:

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

   // do something with the error...

   //automatic cleanup when error goes out of scope
}

Saat kelas dibuat dan dimusnahkan di tumpukan, meninggalkan errorText mengarah ke lokasi yang tidak valid. Dalam kebanyakan kasus, lokasi ini masih akan berisi string kesalahan, tetapi kemungkinan itu hilang dengan cepat saat menulis aplikasi berulir.

Jadi selalu lakukan seperti berikut yang dijawab oleh Shog9 di atas:

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

   // do something with the error...

   //automatic cleanup when error goes out of scope
}
Marius
sumber
7
The _com_errorobjek dibuat pada stack di kedua contoh Anda. Istilah yang Anda cari bersifat sementara . Dalam contoh sebelumnya, objek adalah sementara yang dihancurkan di akhir pernyataan.
Rob Kennedy
Yup, berarti itu. Tapi saya berharap kebanyakan orang setidaknya bisa mengetahuinya dari kode. Secara teknis sementara tidak dihancurkan di akhir pernyataan, tetapi di akhir titik urutan. (yang merupakan hal yang sama dalam contoh ini jadi ini hanya membelah rambut.)
Marius
1
Jika Anda ingin membuatnya aman (mungkin tidak terlalu efisien ), Anda dapat melakukannya di C ++:std::wstring strErrorText = _com_error(hresult).ErrorMessage();
ahmd0
11

Coba ini:

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);
}
David Hanak
sumber
void HandleLastError (hresult)?
Aaron
1
Tentunya Anda bisa membuat adaptasi ini sendiri.
oefe
@Atklin: Jika Anda ingin menggunakan hresult dari parameter, Anda jelas tidak memerlukan baris pertama (GetLastError ()).
David Hanak
4
GetLastError tidak mengembalikan hasil HR. Ia mengembalikan kode kesalahan Win32. Mungkin lebih suka nama PrintLastError karena ini tidak benar-benar menangani apa pun. Dan pastikan untuk menggunakan FORMAT_MESSAGE_IGNORE_INSERTS.
Rob Kennedy
Terima kasih atas bantuan Anda guys :) - sangat dihargai
Aaron
5

Ini lebih penambah mayoritas jawaban, tapi bukannya menggunakan LocalFree(errorText)penggunaan HeapFreefungsi:

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

Dari situs MSDN :

Windows 10 :
LocalFree tidak ada dalam SDK modern, jadi tidak dapat digunakan untuk membebaskan buffer hasil. Sebagai gantinya, gunakan HeapFree (GetProcessHeap (), AllocatedMessage). Dalam hal ini, ini sama dengan memanggil LocalFree di memori.

Pembaruan
Saya menemukan bahwa LocalFreedalam versi 10.0.10240.0 dari SDK (baris 1108 di WinBase.h). Namun, peringatan tersebut masih ada di tautan di atas.

#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

Perbarui 2
Saya juga menyarankan menggunakan FORMAT_MESSAGE_MAX_WIDTH_MASKbendera untuk merapikan jeda baris dalam pesan sistem.

Dari situs MSDN :

FORMAT_MESSAGE_MAX_WIDTH_MASK
Fungsi ini mengabaikan jeda baris reguler dalam teks definisi pesan. Fungsi tersebut menyimpan jeda baris berkode keras dalam teks definisi pesan ke dalam buffer keluaran. Fungsi ini tidak menghasilkan jeda baris baru.

Pembaruan 3
Tampaknya ada 2 kode kesalahan sistem tertentu yang tidak mengembalikan pesan lengkap menggunakan pendekatan yang disarankan:

Mengapa FormatMessage hanya membuat pesan sebagian untuk ERROR_SYSTEM_PROCESS_TERMINATED dan ERROR_UNHANDLED_EXCEPTION kesalahan sistem?

Kerangka Kelas
sumber
4

Berikut adalah versi dari fungsi David yang menangani 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);

}

Oleg Zhylin
sumber
1
Perhatikan bahwa Anda tidak meneruskan ukuran buffer yang benar ke _sntprintf_sdalam kasus UNICODE. Fungsi tersebut mengambil jumlah karakter, jadi Anda ingin _countofatau ARRAYSIZEalias, sizeof(buffer) / sizeof(buffer[0])bukan sizeof.
ThFabba
4

Sejak c ++ 11, Anda dapat menggunakan pustaka standar sebagai ganti FormatMessage:

#include <system_error>

std::string message = std::system_category().message(hr)
Kronial
sumber
2

Seperti yang ditunjukkan dalam jawaban lain:

  • FormatMessagemengambil DWORDhasil bukan HRESULT(biasanya GetLastError()).
  • LocalFree diperlukan untuk melepaskan memori yang telah dialokasikan oleh FormatMessage

Saya mengambil poin di atas dan menambahkan beberapa lagi untuk jawaban saya:

  • Bungkus FormatMessagedalam kelas untuk mengalokasikan dan melepaskan memori sesuai kebutuhan
  • Gunakan kelebihan operator (misalnya operator LPTSTR() const { return ...; }agar kelas Anda dapat digunakan sebagai string
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;

};

Temukan versi yang lebih lengkap dari kode di atas di sini: https://github.com/stephenquan/FormatMessage

Dengan kelas di atas, penggunaannya sederhana:

    std::wcout << (LPTSTR) CFormatMessage(GetLastError()) << L"\n";
Stephen Quan
sumber
0

Kode di bawah ini adalah kode yang setara dengan C ++ yang telah saya tulis berbeda dengan Microsoft's ErrorExit () tetapi sedikit diubah untuk menghindari semua makro dan menggunakan unicode. Idenya di sini adalah untuk menghindari cast dan mallocs yang tidak perlu. Saya tidak bisa lepas dari semua pemain C tapi ini yang terbaik yang bisa saya kumpulkan. Berkenaan dengan FormatMessageW (), yang membutuhkan pointer untuk dialokasikan oleh fungsi format dan Id Error dari GetLastError (). Penunjuk setelah static_cast dapat digunakan seperti penunjuk wchar_t biasa.

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

sumber