FormatMessage () 'ı C ++' da doğru şekilde nasıl kullanmalıyım?


90

Olmadan :

  • MFC
  • ATL

A FormatMessage()için hata metnini almak için nasıl kullanabilirim 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);
 }

Yanıtlar:


134

İşte bir hata mesajı için sistemden geri almanın uygun yolu HRESULT(bu durumda hresult olarak adlandırılır veya yerine koyabilirsiniz 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;
}

Bununla David Hanak'ın cevabı arasındaki temel fark FORMAT_MESSAGE_IGNORE_INSERTSbayrak kullanımıdır . MSDN, eklemelerin nasıl kullanılması gerektiği konusunda biraz belirsizdir, ancak Raymond Chen , sistemin hangi eklemeleri beklediğini bilmenin hiçbir yolu olmadığından, bir sistem mesajını alırken bunları asla kullanmaman gerektiğini belirtiyor .

FWIW, Visual C ++ kullanıyorsanız, _com_errorsınıfı kullanarak hayatınızı biraz daha kolaylaştırabilirsiniz :

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

   //automatic cleanup when error goes out of scope
}

Bildiğim kadarıyla MFC veya ATL'nin parçası değil.


8
Dikkat: Bu kod, Win32 hata kodu yerine hResult kullanır: bunlar farklı şeylerdir! Gerçekte meydana gelenden tamamen farklı bir hatanın metnini alabilirsiniz.
Andrei Belogortseff

1
Mükemmel nokta, @Andrei - gerçekten de, hata bile olduğunu bir ise bir Win32 hata, bu rutin sadece başarılı olacaktır sistem kodunu incelemek, sağlam hata işleme mekanizması hata kaynağı farkında olması gerekir - Hata FormatMessage'ı çağırmadan önce ve belki bunun yerine diğer kaynakları sorgulayabilirsiniz.
Shog9

1
@AndreiBelogortseff Her durumda ne kullanacağımı nasıl bilebilirim? Örneğin, RegCreateKeyExbir LONG. Dokümanları FormatMessage, hatayı geri almak için kullanabileceğimi söylüyor , ancak bunu LONGbir HRESULT.
csl

FormatMessage (), geçerli bir hata kodu olduğu varsayılan bir işaretsiz tamsayı olan DWORD, @csl alır . Tüm dönüş değerleri - veya bu konuyla ilgili HRESULTS - geçerli hata kodları olmayacaktır; sistem, işlevi çağırmadan önce doğruladığınızı varsayar. RegCreateKeyEx için dokümanlar, dönüş değerinin ne zaman bir hata olarak yorumlanabileceğini belirtmelidir ... Önce bu denetimi yapın ve sonra yalnızca FormatMessage'ı çağırın.
Shog9

1
MSDN aslında şimdi aynı kodu kendi sürümlerine sağlıyor .
ahmd0

14

Aşağıdakileri yapamayacağınızı unutmayın:

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

   // do something with the error...

   //automatic cleanup when error goes out of scope
}

Sınıf oluşturulduğunda ve yığında yok edildiğinde errorText'i geçersiz bir konuma işaret edecek şekilde bırakır. Çoğu durumda bu konum hala hata dizesini içerecektir, ancak iş parçacıklı uygulamalar yazılırken bu olasılık hızla azalır.

Öyleyse her zaman yukarıdaki Shog9 tarafından cevaplandığı gibi yapın:

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

   // do something with the error...

   //automatic cleanup when error goes out of scope
}

7
_com_errorNesne içinde yığın oluşturulur hem sizin örnekler. Aradığınız terim geçicidir . Önceki örnekte, nesne geçicidir ve ifadenin sonunda yok edilir.
Rob Kennedy

Evet, bunu kastetti. Ama umarım çoğu insan bunu en azından koddan anlayabilir. Teknik olarak geçiciler ifadenin sonunda değil, sıralama noktasının sonunda yok edilir. (bu örnekte de aynı şey olduğu için bu sadece saçları bölmektir.)
Marius

1
Güvenli hale getirmek istiyorsanız (belki çok verimli değil ) bunu C ++ 'da yapabilirsiniz:std::wstring strErrorText = _com_error(hresult).ErrorMessage();
ahmd0

11

Bunu dene:

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
Elbette bu uyarlamaları kendiniz yapabilirsiniz.
oefe

@Atklin: Bir parametreden hresult kullanmak istiyorsanız, ilk satıra (GetLastError ()) ihtiyacınız yok.
David Hanak

4
GetLastError bir HResult döndürmez. Win32 hata kodu döndürür. Bu aslında olmadığından adı PrintLastError tercih edebilirsiniz işlemek şey. FORMAT_MESSAGE_IGNORE_INSERTS kullandığınızdan emin olun.
Rob Kennedy

Yardımlarınız için teşekkürler arkadaşlar :) - çok minnettarız
Aaron

5

Bu daha çok cevapların çoğuna bir eklemedir, ancak işlevi LocalFree(errorText)kullanmak yerine kullanın HeapFree:

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

MSDN sitesinden :

Windows 10 :
LocalFree, modern SDK'da bulunmadığından sonuç arabelleğini boşaltmak için kullanılamaz. Bunun yerine HeapFree (GetProcessHeap (), ayrılanMessage) kullanın. Bu durumda, bu bellekte LocalFree'yi çağırmakla aynıdır.

Güncelleştirme
bunu buldum LocalFreeSDK (WinBase.h çizgi 1108) sürümünde 10.0.10240.0 içindedir. Bununla birlikte, uyarı hala yukarıdaki bağlantıda mevcuttur.

#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

Güncelleme 2
Ayrıca FORMAT_MESSAGE_MAX_WIDTH_MASK, sistem mesajlarındaki satır sonlarını düzenlemek için bayrağı kullanmanızı öneririm .

MSDN sitesinden :

FORMAT_MESSAGE_MAX_WIDTH_MASK
İşlev, mesaj tanımlama metnindeki normal satır sonlarını göz ardı eder. İşlev, mesaj tanımlama metnindeki sabit kodlanmış satır sonlarını çıktı arabelleğinde depolar. Fonksiyon yeni satır sonu oluşturmaz.

Güncelleme 3
Önerilen yaklaşımı kullanarak tam mesajı döndürmeyen 2 belirli sistem hata kodu var gibi görünüyor:

FormatMessage neden yalnızca ERROR_SYSTEM_PROCESS_TERMINATED ve ERROR_UNHANDLED_EXCEPTION sistem hataları için kısmi mesajlar oluşturur?


5

C ++ 11'den beri, standart kitaplığı aşağıdakiler yerine kullanabilirsiniz FormatMessage:

#include <system_error>

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

4

İşte David'in Unicode'u işleyen işlevinin bir sürümü

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 durumunda doğru arabellek boyutunu geçirmediğinizi unutmayın . İstediğiniz böylece fonksiyon, karakter sayısını alır _countofveya ARRAYSIZEdiğer adıyla sizeof(buffer) / sizeof(buffer[0])yerine sizeof.
ThFabba

2

Diğer cevaplarda da belirtildiği gibi:

  • FormatMessage(tipik olarak ) DWORDdeğil bir sonuç alır .HRESULTGetLastError()
  • LocalFree tarafından ayrılan belleği serbest bırakmak için gereklidir FormatMessage

Yukarıdaki noktaları aldım ve cevabım için birkaç tane daha ekledim:

  • FormatMessageGerektiği gibi bellek ayırmak ve serbest bırakmak için bir sınıfa sarın
  • Operatör aşırı yüklemesini kullanın (örneğin operator LPTSTR() const { return ...; }, sınıfınızın bir dizge olarak kullanılabilmesi için)
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;

};

Yukarıdaki kodun daha eksiksiz bir sürümünü burada bulun: https://github.com/stephenquan/FormatMessage

Yukarıdaki sınıfla kullanım basitçe:

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

0

Aşağıdaki kod, Microsoft'un ErrorExit () 'inin aksine yazdığım C ++ eşdeğeridir ancak tüm makrolardan kaçınmak ve unicode kullanmak için biraz değiştirilmiştir. Buradaki fikir, gereksiz yayınlardan ve malloc'lardan kaçınmaktır. Tüm C dökümlerinden kaçamadım ama toplayabildiğim en iyisi bu. Format işlevi ve GetLastError () 'dan Hata Kimliği tarafından ayrılacak bir işaretçi gerektiren FormatMessageW () ile ilgili. Static_cast sonrasındaki gösterici normal bir wchar_t işaretçisi gibi kullanılabilir.

#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);
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.