GetLastError () tarafından döndürülen hata kodundan hata mesajı nasıl alınır?


138

Bir Windows API çağrısından sonra, son hata mesajını metin biçiminde nasıl alabilirim?

GetLastError() kısa mesaj değil, tamsayı değeri döndürür.


Visual Studio'nun araç bölümünde, hata ayıklama için yalnızca hatadan mesaja ihtiyacınız olduğunda bunu oldukça iyi yapan bir exe hatası araması vardır.
ColdCat

@ColdCat: Hata ayıklamak için sadece bir @err,hrsaat eklemek ve hata ayıklayıcının son hata kodunu otomatik olarak insan tarafından okunabilir bir temsile dönüştürmesini sağlamak çok daha kolaydır . ,hrBiçim belirteci ayrılmaz bir değere değerlendirir, örneğin bir herhangi ifadesi için çalışır 5,hrizle gösterecektir "Error_access_denıed: Erişim engellendi." .
2017

2
Gönderen GetLastError()belgeler: " Sistem hata kodları için bir hata dizesi edinmek kullanın FormatMessage()işlevini. ". Bkz alınıyor Son-Hata Kodu MSDN'deki örnek.
Remy Lebeau

Yanıtlar:


145
//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
Aslında (LPSTR)&messageBufferbu durumda geçmeniz gerektiğine inanıyorum , aksi takdirde FormatMessageA'nın değerini tahsis edilen tampona işaret edecek şekilde değiştirmesinin bir yolu yoktur.
Kylotan

2
Oh, vay, evet bu biraz tuhaf. İşaretçiyi nasıl değiştirir? Ama ona işaretçinin adresini (işaretçi-işaretçiye) iletmek, ancak onu normal bir işaretçiye çevirmek ... Win32 tuhaflığı. Uyarılar için teşekkürler, kendi kod tabanımda (ve cevabımda) düzelttim. Çok ince bir yakalama.
Jamin Grey

1
Çok teşekkür ederim, örneğiniz MSDN'den çok daha net. Dahası, MSDN'den olanı bile derleyemedi. Bazı içerir strsafe.hdeğildir başlığını güvenli bunun bir derleyici hataları bir demet neden hiç winuser.hve winbase.h.
Hi-Angel

2
Bazı hata kimlikleri desteklenmez. Örneğin, 0x2EE7, ERROR_INTERNET_NAME_NOT_RESOLVED, FormatMessage'ı çağırırken yeni bir hataya neden oluyor: 0x13D, Sistem,% 2 için ileti dosyasında 0x% 1 ileti numarası için ileti metni bulamıyor.
Brent

3
Bu uygulamayla ilgili sorunlar: 1 GetLastErrorpotansiyel olarak çok geç deniyor. 2Unicode desteği yok. 3İstisna güvenlik garantileri uygulamadan istisnaların kullanılması.
2017

65

Bazı yorumları dikkate alacak şekilde güncellendi (11/2017).

Kolay örnek:

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 - Örnek, UNICODE tanımlı olarak derlediğinizi varsayar. 'FormatMessage', uygulamanın nasıl derlendiğine bağlı olarak, Ansi / MBCS karakter arabellekleri için 'FormatMessageA' ya da UTF16 / UNICODE arabellekleri için 'FormatMessageW' şeklinde genişleyen bir makrodur. Çıktı tampon tipiyle (wchar_t) eşleşen sürümü açıkça çağırmak için yukarıdaki örneği düzenleme özgürlüğünü aldım.
Bukes

2
FORMAT_MESSAGE_IGNORE_INSERTS ekleyin: "Biçim dizesinin kontrolü sizde değilse,% 1'in soruna neden olmasını önlemek için FORMAT_MESSAGE_IGNORE_INSERTS'i geçmelisiniz." blogs.msdn.microsoft.com/oldnewthing/20071128-00/?p=24353
Roi Danton

1
Bu uygulamayla ilgili sorunlar: 1Önemli FORMAT_MESSAGE_IGNORE_INSERTSbayrağın belirlenememesi. 2 GetLastErrorpotansiyel olarak çok geç aranır. 3Mesajın keyfi olarak 256 kod birimiyle sınırlandırılması. 4Hata işleme yok.
2017

1
Sizeof (buf), ARRAYSIZE (buf) olmalıdır, çünkü FormatMessage arabelleğin boyutunu bayt olarak değil TCHAR'larda beklemektedir.
Kai

1
256Bunun yerine kullanamaz mıyız (sizeof(buf) / sizeof(wchar_t)? kabul edilebilir ve güvenli mi?
BattleTest



14

GetLastError sayısal bir hata kodu döndürür. Açıklayıcı bir hata mesajı almak için (örneğin, bir kullanıcıya görüntülemek için), FormatMessage'ı çağırabilirsiniz :

// 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 ++ 'da std :: string sınıfını kullanarak arayüzü önemli ölçüde basitleştirebilirsiniz:

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

NOT: Bu işlevler, HRESULT değerleri için de çalışır. DWORD dwErrorCode olan ilk parametreyi HRESULT hResult olarak değiştirmeniz yeterlidir. Kodun geri kalanı değişmeden kalabilir.


Bu uygulamalar, mevcut yanıtlara göre aşağıdaki iyileştirmeleri sağlar:

  • Yalnızca aranacak API referansı değil, eksiksiz örnek kod.
  • Hem C hem de C ++ uygulamaları sağlar.
  • Hem Unicode hem de MBCS proje ayarları için çalışır.
  • Hata kodunu giriş parametresi olarak alır. Bir iş parçacığının son hata kodu yalnızca iyi tanımlanmış noktalarda geçerli olduğundan bu önemlidir. Bir giriş parametresi, arayanın belgelenmiş sözleşmeyi takip etmesine olanak tanır.
  • Uygun istisna güvenliğini uygular. İstisnaları örtük olarak kullanan diğer tüm çözümlerin aksine, bu uygulama, dönüş değeri oluşturulurken bir istisna atılması durumunda bellek sızdırmaz.
  • FORMAT_MESSAGE_IGNORE_INSERTSBayrağın doğru kullanımı . Daha fazla bilgi için FORMAT_MESSAGE_IGNORE_INSERTS bayrağının önemi konusuna bakın .
  • Hataları sessizce görmezden gelen diğer bazı yanıtların aksine uygun hata işleme / hata raporlama.


Bu cevap Stack Overflow Documentation'dan alınmıştır. Aşağıdaki kullanıcılar örneğe katkıda bulunmuştur: stackptr , Ajay , Cody Gray ♦ , IInspectable .


1
Atmak yerine , başarısız aramadan sonra dönüş değerinin nerede olacağını atmanızı std::runtime_erroröneririm . std::system_error(lastError, std::system_category(), "Failed to retrieve error message string.")lastErrorGetLastError()FormatMessage()
zett42

C versiyonunuzu FormatMessagedoğrudan kullanmanın avantajı nedir ?
Micha Wiedenmann

@mic: Bu sormak gibi: "C'deki fonksiyonların avantajı nedir?" Fonksiyonlar, soyutlamalar sağlayarak karmaşıklığı azaltır. Bu durumda, soyutlama parametre sayısını 7'den 3'e düşürür, uyumlu bayrakları ve parametreleri ileterek API'nin belgelenmiş sözleşmesini uygular ve belgelenmiş hata raporlama mantığını kodlar. Kolayca tahmin edilebilen anlambilim ile özlü bir arayüz sağlar ve dokümantasyonu okumak için 11 dakika ayırmanıza gerek kalmaz .
2019

Bu cevabı beğendim, kodun doğruluğuna çok dikkat edildiği çok açık. İki sorum var. 1) Belgelerin önerdiği gibi neden ::HeapFree(::GetProcessHeap(), 0, p)silicide kullanıyorsunuz ::LocalFree(p)? 2) Dokümantasyonun bunu bu şekilde yapmayı söylediğini, ancak reinterpret_cast<LPTSTR>(&psz)katı örtüşme kuralını ihlal etmediğini anlıyorum.
Teh JoE

@teh: Geri bildiriminiz için teşekkür ederiz. Soru 1): Dürüst olmak gerekirse, bunun güvenli olup olmadığını bilmiyorum. HeapFreeBu hala SO Belgelerinde bir konu iken , çağrının kime veya neden yapıldığını hatırlamıyorum . Araştırmam gerekecek (belgeler net görünse de, bunun güvenli olmadığı ). Soru 2): Bu iki yönlüdür. Bir MBCS yapılandırması LPTSTRiçin char*,. Bu alçı her zaman güvenlidir. Bir Unicode konfigürasyonu için wchar_t*, herhangi bir oranda çevrim balıktır. Bunun C'de güvenli olup olmadığını bilmiyorum, ancak büyük olasılıkla C ++ 'da değil. Bunu da araştırmalıyım.
IInspectable

13

Genel olarak, FormatMessagebir Win32 hata kodundan metne dönüştürmek için kullanmanız gerekir .

MSDN belgelerinden :

Bir mesaj dizesini biçimlendirir. Fonksiyon, girdi olarak bir mesaj tanımı gerektirir. Mesaj tanımı, işleve geçirilen bir tampondan gelebilir. Zaten yüklenmiş bir modüldeki bir mesaj tablosu kaynağından gelebilir. Veya arayan, işlevden sistemin mesaj tablosu kaynak (lar) ını mesaj tanımı için aramasını isteyebilir. Fonksiyon, bir mesaj tablosu kaynağında mesaj tanımını bir mesaj tanımlayıcısına ve bir dil tanımlayıcısına göre bulur. İşlev, biçimlendirilmiş mesaj metnini bir çıktı arabelleğine kopyalar ve istenirse tüm gömülü ekleme dizilerini işler.

FormatMessage beyanı:

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

7

C # kullanıyorsanız bu kodu kullanabilirsiniz:

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

Bu kodun çalıştığını onayladım. TS'nin bu cevabı kabul etmesi gerekmez mi?
swdev

2
Daha fazla atış için gerekliyse , Win32Exception ile C # 'da yapmanın daha basit bir yolu var
SerG

2
@swdev: Neden kimse c veya c ++ etiketli bir soruya C # yanıtını kabul etsin ? Mevcut haliyle, bu cevap sorulan soruya hitap bile etmiyor.
2017

1
Bu önerilen cevaba oy verdiğimi hatırlamıyorum. @ Swdev'in mantığındaki bariz kusuru ele aldım. Ama bana inanmayacağına göre, şimdi bunu sana kanıtlayacağım: İşte, bir başka olumsuz oylama yap. Bu benden, çünkü bu cevap - farklı bir soru verilmesi faydalı olsa da - sorulan soru göz önüne alındığında yararlı değil.
2017

2
"Bariz olanın ötesinde sunabileceğiniz yararlı içgörülere sahip olduğunuzu tahmin ediyorum" - Gerçekten, var .
2017

4

Unicode'un yanı sıra MBCS'yi de desteklemeniz gerekiyorsa, Bay C64'ün cevabı yeterli değil. Arabellek TCHAR olarak bildirilmeli ve LPTSTR'ye dönüştürülmelidir. Bu kodun Microsoft'un hata mesajına eklediği sinir bozucu satırsonu ile ilgilenmediğini unutmayın.

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

Ayrıca, kısalık için aşağıdaki yöntemi faydalı buluyorum:

CString GetLastErrorString()
{
    return FormatErrorMessage(GetLastError());
}

CStringC'tor'un bir istisna atması durumunda , bu uygulama çağrı tarafından ayrılan belleği sızdırır FormatMessage.
2017

Doğru, ama bu kodu yıllardır kullandım ve bu hiç sorun olmadı. CString ctor'un atma olasılığının yüksek olduğu tek durum, bellek ayırmada başarısızlıktır ve çoğu MFC kodu - Microsoft tarafından sağlanan şeyler de dahil olmak üzere - bellek yetersiz koşullarını istediğiniz kadar zarif bir şekilde işlemez. Neyse ki çoğu bilgisayarda o kadar çok bellek var ki hepsini kullanmak için oldukça sıkı çalışmanız gerekiyor. Geçici bir CString örneği oluşturan herhangi bir kullanım (bir CString döndürme dahil) bu riski taşır. Bu bir risk / kolaylık değiş tokuşudur. Ayrıca, bir ileti işleyicide gerçekleşirse, istisna yakalanacaktır.
victimofleisure

Bu yorumun çoğu yanlış, üzgünüm. "Benim başıma hiç gelmedi" , özellikle de kodun nasıl başarısız olabileceğini bildiğiniz zaman, çok zayıf bir noktadır. Bellek miktarının da bir işleme ayrılan kullanılabilir adres alanı üzerinde hiçbir etkisi yoktur. RAM yalnızca bir performans optimizasyonudur. NRVO'ya izin vermek için kod yazıldığında kopya-elizyon geçici bir tahsisi engeller. Bir mesaj işleyicide istisnaların yakalanıp yakalanmayacağı, işlem bitliliğine ve harici ayarlara bağlıdır. Risk yönetiminin rahatsızlığa yol açmadığını gösteren bir yanıt gönderdim.
Görülmez

Ayrıca, bir geçici oluştururken hafızanın tükenme riski tartışmalıdır. Bu noktada tüm kaynaklar serbest bırakıldı ve bundan kötü bir şey çıkmayacak.
IInspectable

1
Hayır, üzgünüm kod sizin için yararlı değil. Ancak TBH, benim problemler listemde oldukça düşük.
victimofleisure

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

Ayrıca bir açıklama da ekleyebilir misiniz?
Robert

1
Bu uygulamayla ilgili sorunlar: 1Unicode desteği yok. 2Hata mesajının yanlış biçimlendirilmesi. Arayanın döndürülen dizeyi işlemesi gerekiyorsa, bunu yapabilir. Uygulamanız, arayana seçenek bırakmaz. 3İstisnaların kullanılması, ancak uygun istisna güvenliğinin olmaması. std::stringOperatörlerin istisnalar atması durumunda, tarafından ayrılan tampon FormatMessagesızdırılır. Arayanın referansla bir nesneyi geçmesini sağlamak yerine 4neden basitçe a döndürmüyorsunuz std::string?
2017

1

C ++ 11'den beri, standart kitaplığı aşağıdakiler yerine kullanabilirsiniz 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);
    }
}

GetLastErrorAramanın anlamlı bir sonuç ürettiği yalnızca küçük bir pencere vardır . Bunun C ++ olmasıyla, buradaki tek güvenli seçenek, arayanın son hata kodunu sağlamasıdır. Kodun GetLastError iki kez çağrı sunması kesinlikle yardımcı olmuyor . Ayrıca, uygun olsa da, C ++ 'nın doğasında bulunan geniş karakter desteği eksikliği, error_categoryarayüzü evrensel olarak kullanışlı hale getirmede başarısız olur . Bu sadece C ++ 'nın kaçırılan fırsatların uzun geçmişine katkıda bulunur.
IInspectable

Yararsız çağrı hakkında iyi bir nokta GetLastError. Ama GetLastErrorburada aramakla arayanın onu aramasını sağlamak arasında bir fark görmüyorum . C ++ ve wchar ile ilgili olarak: Umuttan vazgeçmeyin, Microsoft uygulamaların yalnızca UTF-8 olmasına izin vermeye başlıyor .
Chronial

"Hiçbir farkı görmek" - Aşağıdaki çağrı sitesini düşünün: log_error("error", GetLastErrorAsString());. Ayrıca, log_errorilk argümanın bir tür olduğunu düşünün std::string. (Görünmez) dönüşüm c'tor çağrısı GetLastError, aradığınız noktadan itibaren anlamlı bir değer yakalamak için garantilerinizi kaldırdı .
IInspectable

Neden std :: string yapıcısının bir Win32 işlevi çağırdığını düşünüyorsunuz?
Chronial

1
mallocHeapAllocişlem yığını çağırır . İşlem yığını büyütülebilir. Büyümeye gerekiyorsa, sonuçta arayacak VirtualAlloc , yok çağırarak son hata kodunu ayarlayın. Şimdi bu, noktayı tamamen kaçırıyor: C ++ bir mayın tarlasıdır. Bu uygulama, buna ek olarak, zımni garantilerle bir arayüz sağlayarak, basitçe yaşayamayacağı anlamına gelir. Herhangi bir sorun olmadığına inanıyorsanız , önerilen çözümün doğruluğunu kanıtlamanız kolay olacaktır . İyi şanslar.
05'te görülüyor

0

Daha sonra kullanmam gerekeceğinden bunu burada bırakacağım. Montaj, C ve C ++ 'da eşit derecede iyi çalışacak küçük bir ikili uyumlu araç için bir kaynaktır.

GetErrorMessageLib.c (GetErrorMessageLib.dll için derlendi)

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

satır içi sürüm (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 */

dinamik kullanım alanı (hata kodunun geçerli olduğu varsayılır, aksi takdirde -1 kontrolü gerekir):

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

düzenli kullanım durumu (hata kodunun geçerli olduğunu varsayar, aksi takdirde -1 dönüş kontrolü gereklidir):

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

Örneğin, MinGW32'deki gibi montaj gnu ile kullanma (yine, hata kodunun geçerli olduğu varsayılmıştır, aksi takdirde -1 kontrolü gereklidir).

    .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

sonuç: The process cannot access the file because another process has locked a portion of the file.


1
Bu gerçekten yararlı bir şey eklemiyor. Bunun da ötesinde FormatMessage, görünürde hiçbir neden olmaksızın ANSI sürümünü çağırır ve kendisini herhangi bir sebep olmaksızın yine 80 karakterle keyfi olarak sınırlar. Korkarım bu yardımcı olmuyor.
2017

haklısın Unicode sürümünün eksikliğini kimsenin fark etmemesini umuyordum. Gnu'da bir Unicode dizesini nasıl tanımlayacağımı ve çözümümü nasıl değiştireceğimi kontrol edeceğim. sahtekârlık için üzgünüm.
Dmitry

ok unicode sürümü hazır. ve keyfi bir sebep değil; tüm hata mesajları ya 80 karakterden azdır ya da okunmaya değmez ve hata kodu hata mesajından daha önemlidir. 80 karakteri aşan standart hata mesajları yoktur, bu nedenle güvenli bir varsayımdır ve olmadığı zaman belleği sızdırmaz.
Dmitry

3
"tüm hata mesajları 80 karakterden azdır veya okumaya değmez" - Bu yanlış. ERROR_LOCK_VIOLATION(33) için hata mesajı şudur: "Başka bir işlem dosyanın bir bölümünü kilitlediğinden işlem dosyaya erişemiyor." Bir sorunu çözmeye ve bunu bir tanılama günlük dosyasında bulmaya çalışıyorsanız, bu hem açıkça 80 karakterden uzun hem de okumaya değer. Bu cevap, mevcut cevaplara önemli bir değer katmaz.
IInspectable

Bu daha az saf değil. Sadece daha az başarısız oluyor. Arabellek boyutuyla ilgili olan derleme uygulaması hariç (yalnızca 100 kişilik yer varken 200 karakter için yer olduğunu iddia ederek). Yine, bu önemli bir şey eklemiyor, zaten diğer cevapların hiçbirinde değil. Özellikle bu cevaptan daha kötü . Oradaki madde işaretli listeye bakın ve az önce bahsettiklerimin üstüne, önerilen uygulamanızda hangi sorunların olduğunu gözlemleyin.
IInspectable
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.