Bagaimana cara mendapatkan pesan kesalahan dari kode kesalahan yang dikembalikan oleh GetLastError ()?

138

Setelah panggilan Windows API, bagaimana saya bisa mendapatkan pesan kesalahan terakhir dalam bentuk teks?

GetLastError() mengembalikan nilai integer, bukan pesan teks.

Micha Wiedenmann
sumber
ada digunakan untuk menjadi pencarian kesalahan exe di bagian alat di studio visual yang melakukan ini dengan cukup baik ketika Anda hanya perlu pesan dari kesalahan untuk debugging.
ColdCat
@ColdCat: Untuk debugging, jauh lebih mudah untuk hanya menambahkan @err,hrarloji, dan minta debugger secara otomatis mengonversi kode kesalahan terakhir ke representasi yang dapat dibaca manusia. Penentu ,hrformat bekerja untuk setiap ekspresi yang mengevaluasi ke nilai integral, misalnya 5,hrarloji akan menampilkan "ERROR_ACCESS_DENIED: Akses ditolak." .
IInspectable
2
Dari GetLastError()dokumentasi: " Untuk mendapatkan string kesalahan untuk kode kesalahan sistem, gunakan FormatMessage()fungsinya. ". Lihat contoh Mengambil Kode Kesalahan Terakhir di MSDN.
Remy Lebeau

Jawaban:

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;
}
Jamin Grey
sumber
2
Saya percaya Anda benar-benar harus lulus (LPSTR)&messageBufferdalam kasus ini, karena jika tidak, FormatMessageA tidak dapat mengubah nilainya untuk menunjuk ke buffer yang dialokasikan.
Kylotan
2
Oh, wow, ya itu agak aneh. Bagaimana cara memodifikasi pointer? Tapi menyampaikannya alamat penunjuk (pointer-to-a-pointer), tetapi melemparkannya ke pointer biasa ... Keanehan Win32. Terima kasih untuk kepala, perbaiki di basis kode saya sendiri (dan jawaban saya). Tangkapan yang sangat halus.
Jamin Grey
1
Terima kasih banyak, contoh Anda jauh lebih jelas daripada yang dari MSDN. Terlebih lagi, yang dari MSDN bahkan tidak bisa dikompilasi. Ini termasuk beberapa strsafe.hheader, yang tidak aman sama sekali, itu menyebabkan banyak kesalahan kompiler di winuser.hdan winbase.h.
Hi-Angel
2
Beberapa ID kesalahan tidak didukung. Misalnya, 0x2EE7, ERROR_INTERNET_NAME_NOT_RESOLVED menyebabkan kesalahan baru saat memanggil FormatMessage: 0x13D, Sistem tidak dapat menemukan teks pesan untuk nomor pesan 0x% 1 dalam file pesan untuk% 2.
Brent
3
Masalah dengan implementasi ini: 1 GetLastErrorberpotensi dipanggil terlambat. 2Tidak ada dukungan Unicode. 3Penggunaan pengecualian tanpa menerapkan jaminan keamanan pengecualian.
IInspectable
65

Diperbarui (11/2017) untuk mempertimbangkan beberapa komentar.

Contoh mudah:

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);
LeviX
sumber
3
@ Hi-Angel - Contoh ini mengasumsikan bahwa Anda mengkompilasi dengan UNICODE didefinisikan 'FormatMessage' sebenarnya adalah makro yang diperluas ke 'FormatMessageA' untuk buffer karakter Ansi / MBCS, atau 'FormatMessageW' untuk buffer UTF16 / UNICODE, tergantung pada bagaimana aplikasi dikompilasi. Saya mengambil kebebasan mengedit contoh di atas untuk secara eksplisit memanggil versi yang cocok dengan tipe buffer output (wchar_t).
Bukes
2
Tambahkan FORMAT_MESSAGE_IGNORE_INSERTS: "Jika Anda tidak bisa mengendalikan string format, maka Anda harus melewati FORMAT_MESSAGE_IGNORE_INSERTS untuk mencegah% 1 dari menyebabkan masalah." blogs.msdn.microsoft.com/oldnewthing/20071128-00/?p=24353
Roi Danton
1
Masalah dengan implementasi ini: 1Kegagalan untuk menentukan FORMAT_MESSAGE_IGNORE_INSERTSbendera penting . 2 GetLastErrorberpotensi dipanggil terlambat. 3Pembatasan pesan yang sewenang-wenang menjadi 256 unit kode. 4Tidak ada penanganan kesalahan.
IInspectable
1
sizeof (buf) harus ARRAYSIZE (buf) karena FormatMessage mengharapkan ukuran buffer di TCHAR, bukan dalam byte.
Kai
1
Tidak bisakah kita menggunakan 256bukan (sizeof(buf) / sizeof(wchar_t)? apakah itu dapat diterima dan aman?
BattleTested
14

GetLastError mengembalikan kode kesalahan numerik. Untuk mendapatkan pesan kesalahan deskriptif (misalnya, untuk ditampilkan kepada pengguna), Anda dapat memanggil 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);
}

Di C ++, Anda dapat menyederhanakan antarmuka dengan menggunakan kelas 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.");
    }
}

CATATAN: Fungsi-fungsi ini juga berfungsi untuk nilai HRESULT. Cukup ubah parameter pertama dari DWORD dwErrorCode ke HRESULT hResult. Sisa kode dapat tetap tidak berubah.


Implementasi ini memberikan peningkatan berikut atas jawaban yang ada:

  • Kode sampel lengkap, bukan hanya referensi ke API untuk menelepon.
  • Menyediakan implementasi C dan C ++.
  • Bekerja untuk pengaturan proyek Unicode dan MBCS.
  • Mengambil kode kesalahan sebagai parameter input. Ini penting, karena kode kesalahan terakhir utas hanya valid pada titik yang ditentukan dengan baik. Parameter input memungkinkan pemanggil untuk mengikuti kontrak yang didokumentasikan.
  • Menerapkan keamanan pengecualian yang tepat. Tidak seperti semua solusi lain yang secara implisit menggunakan pengecualian, implementasi ini tidak akan membocorkan memori seandainya pengecualian dilemparkan saat membangun nilai kembali.
  • Penggunaan FORMAT_MESSAGE_IGNORE_INSERTSbendera dengan benar. Lihat Pentingnya bendera FORMAT_MESSAGE_IGNORE_INSERTS untuk informasi lebih lanjut.
  • Penanganan / pelaporan kesalahan yang tepat, tidak seperti beberapa jawaban lainnya, yang mengabaikan kesalahan secara diam-diam.


Jawaban ini telah dimasukkan dari Dokumentasi Stack Overflow. Pengguna berikut telah berkontribusi pada contoh: stackptr , Ajay , Cody Grey ♦ , IInspectable .

IInspectable
sumber
1
Alih-alih melempar std::runtime_error, saya menyarankan untuk melempar di std::system_error(lastError, std::system_category(), "Failed to retrieve error message string.")mana lastErrorakan menjadi nilai balik GetLastError()setelah FormatMessage()panggilan gagal .
zett42
Apa keuntungan menggunakan versi C Anda FormatMessagesecara langsung?
Micha Wiedenmann
@mic: Itu seperti bertanya: "Apa kelebihan fungsi dalam C?" Fungsi mengurangi kompleksitas dengan menyediakan abstraksi. Dalam hal ini, abstraksi mengurangi jumlah parameter dari 7 menjadi 3, mengimplementasikan kontrak terdokumentasi dari API dengan melewati flag dan parameter yang kompatibel, dan menyandikan logika pelaporan kesalahan yang terdokumentasi. Ini menyediakan antarmuka yang ringkas, dengan semantik yang mudah ditebak, sehingga Anda tidak perlu berinvestasi 11 menit untuk membaca dokumentasi .
IInspectable
Saya suka jawaban ini, sangat jelas bahwa perhatian yang cermat telah diberikan pada kebenaran kode. Saya punya dua pertanyaan. 1) Mengapa Anda menggunakan ::HeapFree(::GetProcessHeap(), 0, p)dalam deleter, bukan ::LocalFree(p)seperti yang ditunjukkan oleh dokumentasi? 2) Saya menyadari bahwa dokumentasi mengatakan untuk melakukannya dengan cara ini, tetapi tidak reinterpret_cast<LPTSTR>(&psz)melanggar aturan aliasing yang ketat?
Teh JoE
@teh: Terima kasih atas umpan baliknya. Pertanyaan 1): Jujur, saya tidak tahu apakah ini aman. Saya tidak ingat, siapa atau mengapa panggilan HeapFreeitu diperkenalkan, sementara ini masih menjadi topik dalam Dokumentasi SO. Saya harus menyelidikinya (walaupun dokumentasinya tampak jelas, bahwa ini tidak aman). Pertanyaan 2): Ini dua kali lipat. Untuk konfigurasi MBCS, LPTSTRadalah alias untuk char*. Para pemain selalu aman. Untuk konfigurasi Unicode, casting ke wchar_t*adalah mencurigakan pada tingkat apa pun. Saya tidak tahu apakah ini aman di C, tetapi kemungkinan besar tidak di C ++. Saya harus menyelidiki ini juga.
IInspectable
13

Secara umum, Anda perlu menggunakan FormatMessageuntuk mengkonversi dari kode kesalahan Win32 ke teks.

Dari dokumentasi MSDN :

Memformat string pesan. Fungsi ini membutuhkan definisi pesan sebagai input. Definisi pesan dapat berasal dari buffer yang diteruskan ke fungsi. Itu bisa berasal dari sumber daya tabel pesan dalam modul yang sudah dimuat. Atau pemanggil dapat meminta fungsi untuk mencari sumber daya tabel pesan sistem untuk definisi pesan. Fungsi menemukan definisi pesan dalam sumber daya tabel pesan berdasarkan pengidentifikasi pesan dan pengidentifikasi bahasa. Fungsi menyalin teks pesan yang diformat ke buffer output, memproses urutan penyisipan yang disematkan jika diminta.

Deklarasi 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
);
Vinay Sajip
sumber
7

Jika Anda menggunakan c # Anda dapat menggunakan kode ini:

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();
        }
    }
}
rboy
sumber
Saya mengkonfirmasi kode ini berfungsi. Bukankah seharusnya TS menerima jawaban ini?
swdev
2
Jika perlu untuk melempar lebih lanjut, ada cara yang lebih sederhana untuk melakukannya di C # dengan Win32Exception
SerG
2
@swdev: Mengapa orang harus menerima jawaban dalam C # untuk pertanyaan yang ditandai c atau c ++ ? Seperti berdiri, jawaban ini bahkan tidak menjawab pertanyaan yang diajukan.
IInspectable
1
Yah, saya tidak ingat pernah memberikan suara untuk jawaban yang diajukan ini. Saya membahas kelemahan yang jelas dalam logika @ swdev. Tetapi karena Anda tidak akan mempercayai saya, saya sekarang akan membuktikannya kepada Anda: Di sini, lakukan voting lagi. Yang itu dari saya, karena jawaban ini - walaupun mungkin berguna diberikan pertanyaan yang berbeda - sama sekali tidak berguna mengingat pertanyaan yang diajukan.
IInspectable
2
"Saya kira Anda memiliki wawasan yang membantu untuk menawarkan di luar yang jelas" - Memang, saya punya .
IInspectable
4

Jika Anda perlu mendukung MBCS dan juga Unicode, jawaban Mr.C64 tidak cukup. Buffer harus dinyatakan TCHAR, dan dilemparkan ke LPTSTR. Perhatikan bahwa kode ini tidak berurusan dengan baris baru yang menjengkelkan yang ditambahkan Microsoft ke pesan kesalahan.

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

Juga, untuk singkatnya saya menemukan metode berikut berguna:

CString GetLastErrorString()
{
    return FormatErrorMessage(GetLastError());
}
korban liburan
sumber
Dalam hal CStringc'tor melempar pengecualian, implementasi ini bocor memori yang dialokasikan oleh panggilan ke FormatMessage.
IInspectable
Benar, tapi saya sudah menggunakan kode ini selama bertahun-tahun dan itu tidak pernah menjadi masalah. Satu-satunya kasus di mana CString ctor cenderung melempar adalah kegagalan untuk mengalokasikan memori, dan sebagian besar kode MFC - termasuk hal-hal yang disediakan Microsoft - tidak menangani kondisi memori seanggun yang Anda inginkan. Untungnya sebagian besar PC sekarang memiliki begitu banyak memori sehingga Anda harus bekerja sangat keras untuk menggunakannya semuanya. Setiap penggunaan yang menghasilkan instance CString sementara (termasuk mengembalikan CString) berisiko ini. Ini adalah risiko / kenyamanan trade-off. Juga jika itu terjadi dalam penangan pesan, pengecualian akan ditangkap.
korbanofleisure
Sebagian besar komentar ini salah, maaf. "Itu tidak pernah terjadi pada saya" adalah titik lemah untuk dibuat, terutama ketika Anda tahu bagaimana kode bisa gagal. Jumlah memori tidak memiliki dampak pada ruang alamat yang tersedia yang dialokasikan untuk suatu proses. RAM hanyalah optimasi kinerja. Copy-elision mencegah alokasi sementara, ketika kode ditulis untuk memungkinkan NRVO. Apakah pengecualian tertangkap dalam penangan pesan tergantung pada bitness proses dan pengaturan eksternal. Saya telah mengirimkan jawaban yang menunjukkan, bahwa manajemen risiko tidak mengarah pada ketidaknyamanan.
IInspectable
Selain itu, risiko kehabisan memori dalam membangun sementara adalah diperdebatkan. Pada saat itu semua sumber daya telah dibebaskan, dan tidak ada hal buruk yang akan terjadi.
IInspectable
1
Tidak, maaf kode ini tidak berguna untuk Anda. Tapi TBH cukup rendah dalam daftar masalah saya.
korbanofleisure
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);
}
}
Andriy Kuz
sumber
Bisakah Anda menambahkan penjelasan?
Robert
1
Masalah dengan implementasi ini: 1Tidak ada dukungan Unicode. 2Format pesan kesalahan yang tidak sesuai. Jika penelepon perlu memproses string yang dikembalikan, itu bisa dilakukan. Implementasi Anda membuat penelepon tidak memiliki opsi. 3Penggunaan pengecualian tetapi tidak memiliki keamanan pengecualian yang tepat. Dalam hal std::stringoperator mengeluarkan pengecualian, buffer yang dialokasikan oleh FormatMessagebocor. 4Mengapa tidak mengembalikan saja std::stringalih - alih meminta penelepon melewati objek dengan referensi?
IInspectable
1

Karena c ++ 11, Anda dapat menggunakan pustaka standar alih-alih 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);
    }
}
Kronologis
sumber
Hanya ada jendela kecil di mana panggilan GetLastErrormenghasilkan hasil yang bermakna. Dengan ini menjadi C ++, satu-satunya pilihan yang aman di sini adalah membuat pemanggil memberikan kode kesalahan terakhir. Itu tentu tidak membantu, bahwa kode yang disajikan panggilan GetLastError dua kali . Selain itu, walaupun nyaman, kurangnya dukungan karakter yang luas dari C ++ 'gagal membuat error_categoryantarmuka bermanfaat secara universal. Ini hanya menambah sejarah panjang peluang C + yang terlewatkan.
IInspectable
Poin bagus tentang panggilan tak berguna ke GetLastError. Tapi saya tidak melihat perbedaan antara menelepon ke GetLastErrorsini atau meminta penelepon memanggilnya. Mengenai C ++ dan wchar: Jangan mengabaikan harapan, Microsoft mulai mengizinkan aplikasi hanya menjadi UTF-8 .
Chronial
"Saya melihat ada perbedaan" - Pertimbangkan situs panggilan berikut: log_error("error", GetLastErrorAsString());. Juga pertimbangkan bahwa log_errorargumen pertama adalah tipe std::string. Panggilan panggilan konversi (tidak terlihat) baru saja menjatuhkan jaminan Anda untuk mendapatkan nilai yang berarti dari GetLastErrorsaat Anda menyebutnya.
IInspectable
Mengapa Anda berpikir bahwa konstruktor std :: string memanggil fungsi Win32?
Kronologis
1
mallocpanggilan HeapAllocuntuk tumpukan proses. Tumpukan proses bisa ditumbuhkan. Jika perlu tumbuh, pada akhirnya akan memanggil VirtualAlloc , yang tidak mengatur kode kesalahan terakhir thread panggilan. Nah, itu benar-benar kehilangan intinya, yaitu: C ++ adalah ladang ranjau. Implementasi ini hanya menambah itu, dengan menyediakan antarmuka dengan jaminan tersirat itu tidak bisa hidup sampai. Jika Anda yakin tidak ada masalah, mudah bagi Anda untuk membuktikan kebenaran solusi yang diajukan. Semoga berhasil.
IInspectable
0

Saya akan meninggalkan ini di sini karena saya harus menggunakannya nanti. Ini adalah sumber untuk alat kompatibel biner kecil yang akan bekerja sama baiknya di assembly, C dan C ++.

GetErrorMessageLib.c (dikompilasi ke 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;
    }
}

versi sebaris (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 */

usecase dinamis (diasumsikan kode kesalahan valid, jika tidak diperlukan -1 pemeriksaan):

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

kasus penggunaan reguler (menganggap kode kesalahan valid, jika tidak -1 pemeriksaan kembali diperlukan):

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

contoh menggunakan dengan assembly gnu seperti pada MinGW32 (sekali lagi, diasumsikan kode kesalahan valid, jika tidak diperlukan -1 cek).

    .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

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

Dmitry
sumber
1
Ini benar-benar tidak menambahkan sesuatu yang bermanfaat. Di atas semua itu, ia menyebut versi ANSI FormatMessage, tanpa alasan yang jelas, dan secara sewenang-wenang membatasi diri hingga 80 karakter, sekali lagi, tanpa alasan apa pun. Saya khawatir, ini tidak membantu.
IInspectable
Anda benar saya berharap tidak ada yang akan memperhatikan tentang kurangnya versi Unicode. Saya akan memeriksa cara mendefinisikan string Unicode di gnu sebagai dan memodifikasi solusi saya. maaf tentang ketidakjujuran.
Dmitry
ok versi unicode sudah habis. dan itu bukan alasan sewenang-wenang; semua pesan kesalahan kurang dari 80 karakter, atau tidak layak dibaca, dan kode kesalahan lebih penting daripada pesan kesalahan. Tidak ada pesan kesalahan standar yang melebihi 80 karakter sehingga asumsi yang aman, dan ketika tidak, tidak bocor memori.
Dmitry
3
"semua pesan kesalahan kurang dari 80 karakter, atau tidak layak dibaca" - Itu salah. Pesan kesalahan untuk ERROR_LOCK_VIOLATION(33) adalah: "Proses tidak dapat mengakses file karena proses lain telah mengunci sebagian file." Keduanya jelas lebih panjang dari 80 karakter, dan sangat layak dibaca, jika Anda mencoba menyelesaikan masalah dan menemukannya dalam file log diagnostik. Jawaban ini tidak menambah nilai substansial atas jawaban yang ada.
IInspectable
Ini tidak kalah naif. Itu lebih jarang gagal. Kecuali untuk implementasi perakitan yang terletak tentang ukuran buffer (mengklaim memiliki ruang untuk 200 karakter, ketika hanya memiliki ruang untuk 100). Sekali lagi, ini tidak menambahkan sesuatu yang substansial, itu belum ada di jawaban lain. Secara khusus, ini lebih buruk daripada jawaban ini . Lihat daftar peluru di sana dan amati, masalah apa yang telah Anda implementasikan, di atas yang saya tunjukkan.
IInspectable