Làm cách nào để nhận thông báo lỗi từ mã lỗi được GetLastError () trả về?


137

Sau cuộc gọi API Windows, làm cách nào tôi có thể nhận được thông báo lỗi cuối cùng ở dạng văn bản?

GetLastError() trả về một giá trị số nguyên, không phải là một tin nhắn văn bản.


có một lỗi tìm kiếm exe trong phần công cụ trong visual studio, điều này thực hiện điều này khá tốt khi bạn chỉ cần thông báo từ lỗi để gỡ lỗi.
ColdCat

@ColdCat: Để gỡ lỗi, việc thêm @err,hrđồng hồ sẽ dễ dàng hơn rất nhiều và trình gỡ lỗi sẽ tự động chuyển đổi mã lỗi cuối cùng thành biểu diễn có thể đọc được. Trình ,hrxác định định dạng hoạt động cho bất kỳ biểu thức nào ước tính giá trị tích phân, ví dụ: 5,hrđồng hồ sẽ hiển thị "ERROR_ACCESS_DENIED: Truy cập bị từ chối." .
Không thể phát hiện

2
Từ GetLastError()tài liệu: " Để có được chuỗi lỗi cho mã lỗi hệ thống, hãy sử dụng FormatMessage()hàm. ". Xem ví dụ Lấy mã lỗi cuối cùng trên MSDN.
Rémy Lebeau

Câu trả lời:


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
Tôi tin rằng bạn thực sự cần phải vượt qua (LPSTR)&messageBuffertrong trường hợp này, vì nếu không thì không có cách nào FormatMessageA có thể thay đổi giá trị của nó để trỏ đến bộ đệm được phân bổ.
Kylotan

2
Ồ, wow, vâng thật là kỳ lạ. Làm thế nào nó sẽ sửa đổi con trỏ? Nhưng truyền cho nó địa chỉ của con trỏ (con trỏ tới con trỏ), nhưng chuyển nó thành một con trỏ thông thường ... Sự kỳ lạ của Win32. Cảm ơn những người đứng đầu, đã sửa nó trong cơ sở mã của riêng tôi (và câu trả lời của tôi). Bắt rất tinh tế.
Jamin Gray

1
Cảm ơn bạn rất nhiều, ví dụ của bạn rõ ràng hơn nhiều so với từ MSDN. Hơn nữa, một từ MSDN thậm chí không thể biên dịch. Nó bao gồm một số strsafe.htiêu đề, hoàn toàn không an toàn , nó gây ra một loạt lỗi biên dịch trong winuser.hwinbase.h.
Hi-Angel

2
Một số ID lỗi không được hỗ trợ. Ví dụ: 0x2EE7, ERROR_INTERNET_NAME_NOT_RESOLVED gây ra lỗi mới khi gọi FormatMessage: 0x13D, Hệ thống không thể tìm thấy văn bản tin nhắn cho số tin nhắn 0x% 1 trong tệp tin nhắn cho% 2.
Brent

3
Các vấn đề với việc thực hiện này: 1 GetLastErrorcó khả năng được gọi là quá muộn. 2Không hỗ trợ Unicode. 3Sử dụng các ngoại lệ mà không thực hiện đảm bảo an toàn ngoại lệ.
IInspectable

65

Cập nhật (11/2017) để xem xét một số ý kiến.

Ví dụ dễ dàng:

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 - Ví dụ giả định rằng bạn đang biên dịch với UNICODE được xác định. 'FormatMessage' thực sự là một macro mở rộng thành 'FormatMessageA' cho bộ đệm ký tự Ansi / MBCS hoặc 'FormatMessageW' cho bộ đệm UTF16 / UNICODE, tùy thuộc vào cách ứng dụng được biên dịch. Tôi đã tự do chỉnh sửa ví dụ trên để gọi một cách rõ ràng phiên bản phù hợp với loại bộ đệm đầu ra (wchar_t).
Bukes

2
Thêm FORMAT_MESSAGE_IGNORE_INSERTS: "Nếu bạn không kiểm soát chuỗi định dạng, thì bạn phải vượt qua FORMAT_MESSAGE_IGNORE_INSERTS để ngăn% 1 gây rắc rối." blog.msdn.microsoft.com/oldnewthing/20071128-00/?p=24353
Roi Danton

1
Các vấn đề với việc thực hiện này: 1Không chỉ định FORMAT_MESSAGE_IGNORE_INSERTScờ quan trọng . 2 GetLastErrorcó khả năng gọi là quá muộn. 3Hạn chế tùy ý của tin nhắn đến 256 đơn vị mã. 4Không xử lý lỗi.
IInspectable

1
sizeof (buf) phải là ARRAYSIZE (buf) vì FormatMessage mong đợi kích thước của bộ đệm trong TCHAR, không phải bằng byte.
Kai

1
Chúng ta không thể sử dụng 256thay thế (sizeof(buf) / sizeof(wchar_t)? Có chấp nhận và an toàn không?
BattleTested



14

GetLastError trả về mã lỗi số. Để có được thông báo lỗi mô tả (ví dụ: để hiển thị cho người dùng), bạn có thể gọi 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);
}

Trong C ++, bạn có thể đơn giản hóa giao diện một cách đáng kể bằng cách sử dụng lớp 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.");
    }
}

LƯU Ý: Các chức năng này cũng hoạt động cho các giá trị HRESULT. Chỉ cần thay đổi tham số đầu tiên từ DWORD dwErrorCode sang HRESULT hResult. Phần còn lại của mã có thể không thay đổi.


Những triển khai này cung cấp các cải tiến sau so với các câu trả lời hiện có:

  • Hoàn thành mã mẫu, không chỉ là tham chiếu đến API để gọi.
  • Cung cấp cả triển khai C và C ++.
  • Hoạt động cho cả cài đặt dự án Unicode và MBCS.
  • Lấy mã lỗi làm tham số đầu vào. Điều này rất quan trọng, vì mã lỗi cuối cùng của một luồng chỉ có giá trị tại các điểm được xác định rõ. Một tham số đầu vào cho phép người gọi thực hiện theo hợp đồng được ghi lại.
  • Thực hiện an toàn ngoại lệ thích hợp. Không giống như tất cả các giải pháp khác sử dụng ngoại lệ, việc triển khai này sẽ không rò rỉ bộ nhớ trong trường hợp ngoại lệ được đưa ra trong khi xây dựng giá trị trả về.
  • Sử dụng FORMAT_MESSAGE_IGNORE_INSERTScờ đúng cách. Xem Tầm quan trọng của cờ FORMAT_MESSAGE_IGNORE_INSERTS để biết thêm thông tin.
  • Xử lý lỗi / báo cáo lỗi đúng, không giống như một số câu trả lời khác, mà âm thầm bỏ qua lỗi.


Câu trả lời này đã được kết hợp từ Tài liệu chồng chéo. Những người dùng sau đây đã đóng góp vào ví dụ: stackptr , Ajay , Cody Grey ♦ , IInspectable .


1
Thay vì ném std::runtime_error, tôi đề nghị ném std::system_error(lastError, std::system_category(), "Failed to retrieve error message string.")ở đâu lastErrorsẽ là giá trị trả về GetLastError()sau khi FormatMessage()cuộc gọi thất bại .
zett42

Lợi thế của việc sử dụng phiên bản C của bạn FormatMessagetrực tiếp là gì?
Micha Wiedenmann

@mic: Điều đó giống như hỏi: "Lợi thế của các chức năng trong C là gì?" Các chức năng giảm độ phức tạp bằng cách cung cấp trừu tượng. Trong trường hợp này, sự trừu tượng hóa làm giảm số lượng tham số từ 7 xuống còn 3, thực hiện hợp đồng được ghi lại bằng API bằng cách chuyển các cờ và tham số tương thích và mã hóa logic báo cáo lỗi được ghi lại. Nó cung cấp một giao diện ngắn gọn, với ngữ nghĩa dễ đoán, không cần bạn phải đầu tư 11 phút để đọc tài liệu .
IInspectable

Tôi thích câu trả lời này, rất rõ ràng rằng sự chú ý cẩn thận đã được trả cho tính chính xác của mã. Tôi có hai câu hỏi. 1) Tại sao bạn sử dụng ::HeapFree(::GetProcessHeap(), 0, p)trong deleter thay vì ::LocalFree(p)như tài liệu gợi ý? 2) Tôi nhận ra rằng tài liệu nói rằng hãy làm theo cách này, nhưng không reinterpret_cast<LPTSTR>(&psz)vi phạm quy tắc răng cưa nghiêm ngặt?
Te JoE

@teh: Cảm ơn bạn đã phản hồi. Câu hỏi 1): Thành thật mà nói, tôi không biết điều này có an toàn không. Tôi không nhớ, ai hoặc tại sao cuộc gọi đến HeapFreeđược giới thiệu, trong khi đây vẫn là một chủ đề trong Tài liệu SO. Tôi sẽ phải điều tra (mặc dù tài liệu dường như rõ ràng, rằng điều này không an toàn). Câu 2): Đây là hai lần. Đối với cấu hình MBCS, LPTSTRlà bí danh cho char*. Diễn viên đó luôn an toàn. Đối với cấu hình Unicode, việc chuyển sang trạng thái wchar_t*tanh là bất kỳ giá nào. Tôi không biết điều này có an toàn trong C không, nhưng rất có thể nó không có trong C ++. Tôi cũng sẽ phải điều tra việc này.
IInspectable

13

Nói chung, bạn cần sử dụng FormatMessage để chuyển đổi từ mã lỗi Win32 sang văn bản.

Từ tài liệu MSDN :

Định dạng một chuỗi tin nhắn. Hàm này yêu cầu một định nghĩa thông điệp là đầu vào. Định nghĩa thông báo có thể đến từ một bộ đệm được truyền vào hàm. Nó có thể đến từ một tài nguyên bảng thông báo trong một mô-đun đã được tải. Hoặc người gọi có thể yêu cầu chức năng tìm kiếm (các) tài nguyên bảng thông báo của hệ thống để xác định định nghĩa thông báo. Hàm tìm định nghĩa thông báo trong tài nguyên bảng thông báo dựa trên mã định danh thư và mã định danh ngôn ngữ. Hàm sao chép văn bản thông báo được định dạng vào bộ đệm đầu ra, xử lý bất kỳ chuỗi chèn được nhúng nào nếu được yêu cầu.

Tuyên bố của 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
);

7

Nếu bạn đang sử dụng c #, bạn có thể sử dụng mã này:

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

Tôi xác nhận mã này hoạt động. Không nên TS chấp nhận câu trả lời này?
swdev

2
Nếu cần thiết để ném xa hơn, có một cách đơn giản hơn để làm điều đó trong C # với Win32Exception
SerG

2
@swdev: Tại sao mọi người nên chấp nhận câu trả lời trong C # cho câu hỏi được gắn thẻ c hoặc c ++ ? Vì thế, câu trả lời này thậm chí không giải quyết được câu hỏi đang được hỏi.
Không thể phát hiện

1
Chà, tôi không nhớ là đã bỏ phiếu cho câu trả lời được đề xuất này. Tôi đã giải quyết lỗ hổng rõ ràng trong logic của @ swdev. Nhưng vì bạn sẽ không tin tôi, giờ tôi sẽ chứng minh điều đó với bạn: Ở đây, có một cuộc bỏ phiếu khác. Đó là câu trả lời của tôi, bởi vì câu trả lời này - trong khi nó có thể hữu ích khi đưa ra một câu hỏi khác - đơn giản là không hữu ích khi đưa ra câu hỏi đang được hỏi.
IInspectable 8/8/17

2
"Tôi đoán bạn có những hiểu biết hữu ích để cung cấp vượt quá rõ ràng" - Thật vậy, tôi có .
IInspectable 8/8/17

4

Nếu bạn cần hỗ trợ MBCS cũng như Unicode, câu trả lời của Mr.C64 là không đủ. Bộ đệm phải được khai báo TCHAR và chuyển sang LPTSTR. Lưu ý rằng mã này không xử lý dòng mới gây phiền nhiễu mà Microsoft thêm vào thông báo lỗi.

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

Ngoài ra, để cho ngắn gọn, tôi thấy phương pháp sau hữu ích:

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

Trong trường hợp CStringc'tor ném ngoại lệ, việc triển khai này sẽ rò rỉ bộ nhớ được cấp phát bởi lệnh gọi đến FormatMessage.
Không thể phát hiện

Đúng, nhưng tôi đã sử dụng mã này trong nhiều năm và nó không bao giờ là vấn đề. Trường hợp duy nhất mà trình điều khiển CString có khả năng ném là không phân bổ bộ nhớ và hầu hết mã MFC - bao gồm các công cụ do Microsoft cung cấp - không xử lý các điều kiện bộ nhớ một cách duyên dáng như bạn muốn. May mắn thay, hầu hết các PC hiện nay có rất nhiều bộ nhớ mà bạn phải làm việc khá chăm chỉ để sử dụng tất cả. Bất kỳ việc sử dụng nào tạo ra một cá thể CString tạm thời (bao gồm cả việc trả lại một CString) đều có rủi ro này. Đó là một sự đánh đổi rủi ro / thuận tiện. Ngoài ra nếu nó xảy ra trong một trình xử lý tin nhắn, ngoại lệ sẽ bị bắt.
Victofleợi

Hầu hết các bình luận này là sai, xin lỗi. "Nó không bao giờ xảy ra với tôi" là một điểm yếu chết tiệt, đặc biệt là khi bạn biết làm thế nào mã có thể thất bại. Dung lượng bộ nhớ không có tác động đến không gian địa chỉ khả dụng được phân bổ cho một quy trình. RAM chỉ là một tối ưu hóa hiệu suất. Sao chép ngăn chặn phân bổ tạm thời, khi mã được viết để cho phép NRVO. Việc có hay không ngoại lệ được bắt gặp trong một trình xử lý tin nhắn tùy thuộc vào độ bit của quá trình và các cài đặt bên ngoài. Tôi đã gửi một câu trả lời cho thấy rằng quản lý rủi ro không dẫn đến sự bất tiện.
IInspectable

Bên cạnh đó, nguy cơ hết bộ nhớ trong việc xây dựng tạm thời là không cần thiết. Tại thời điểm đó, tất cả các tài nguyên đã được giải phóng, và sẽ không có gì xấu xảy ra.
IInspectable

1
Không, tôi xin lỗi, mã không hữu ích cho bạn. Nhưng TBH nó khá thấp trong danh sách các vấn đề của tôi.
nạn nhân

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

Bạn cũng có thể thêm một số lời giải thích?
Robert

1
Các vấn đề với việc thực hiện này: 1Không hỗ trợ Unicode. 2Định dạng không phù hợp của thông báo lỗi. Nếu người gọi cần xử lý chuỗi trả về, nó có thể làm như vậy. Việc thực hiện của bạn khiến người gọi không có tùy chọn. 3Sử dụng các ngoại lệ nhưng thiếu an toàn ngoại lệ thích hợp. Trong trường hợp các std::stringtoán tử đưa ra các ngoại lệ, bộ đệm được cấp phát FormatMessagebị rò rỉ. 4Tại sao không chỉ đơn giản là trả về một std::stringthay vì yêu cầu người gọi vượt qua một đối tượng bằng cách tham chiếu?
Không thể phát hiện

1

Kể từ c ++ 11, bạn có thể sử dụng thư viện chuẩn thay vì 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);
    }
}

Chỉ có một cửa sổ nhỏ nơi cuộc gọi GetLastErrortạo ra một kết quả có ý nghĩa. Với điều này là C ++, tùy chọn an toàn duy nhất ở đây là để người gọi cung cấp mã lỗi cuối cùng. Nó chắc chắn không giúp được gì, rằng mã trình bày các cuộc gọi GetLastError hai lần . Ngoài ra, trong khi thuận tiện, việc thiếu hỗ trợ ký tự rộng của C ++ không thể khiến error_categorygiao diện trở nên hữu dụng. Điều này chỉ thêm vào lịch sử lâu dài của C ++ về các cơ hội bị bỏ lỡ.
IInspectable

Điểm tốt về cuộc gọi vô dụng đến GetLastError. Nhưng tôi thấy không có sự khác biệt giữa gọi GetLastErrorở đây hoặc có người gọi gọi nó. Về C ++ và wchar: Đừng từ bỏ hy vọng, Microsoft đang bắt đầu cho phép các ứng dụng chỉ là UTF-8 .
Biên niên sử

"Tôi thấy không có sự khác biệt" - Hãy xem xét trang web cuộc gọi sau : log_error("error", GetLastErrorAsString());. Cũng xem xét log_errorđối số đầu tiên đó là loại std::string. Cuộc gọi c'tor chuyển đổi (vô hình) vừa loại bỏ bảo đảm của bạn để nắm bắt một giá trị có ý nghĩa từ GetLastErrorthời điểm bạn đang gọi nó.
IInspectable

Tại sao bạn nghĩ rằng hàm tạo std :: string gọi hàm Win32?
Biên niên sử

1
mallocgọi HeapAlloccho quá trình đống. Heap quá trình là có thể phát triển. Nếu nó cần phát triển, cuối cùng nó sẽ gọi VirtualAlloc , mã này đặt mã lỗi cuối cùng của chuỗi gọi. Bây giờ điều đó hoàn toàn thiếu điểm, đó là: C ++ là một bãi mìn. Việc triển khai này chỉ thêm vào đó, bằng cách cung cấp một giao diện với ngụ ý đảm bảo đơn giản là nó không thể sống theo. Nếu bạn tin rằng không có vấn đề gì, bạn sẽ dễ dàng chứng minh tính đúng đắn của giải pháp được đề xuất. Chúc may mắn.
IInspectable

0

Tôi sẽ để nó ở đây vì tôi sẽ cần sử dụng nó sau. Đây là một nguồn cho một công cụ tương thích nhị phân nhỏ sẽ hoạt động tốt như nhau trong lắp ráp, C và C ++.

GetErrorMessageLib.c (được biên dịch thành 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;
    }
}

phiên bản nội tuyến (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 động (giả sử rằng mã lỗi là hợp lệ, nếu không thì cần kiểm tra -1):

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

trường hợp sử dụng thông thường (giả sử mã lỗi là hợp lệ, nếu không thì cần kiểm tra trả lại -1):

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

ví dụ sử dụng với gnu lắp ráp như trong MinGW32 (một lần nữa, giả sử rằng mã lỗi là hợp lệ, nếu không thì cần kiểm tra -1).

    .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

kết quả: The process cannot access the file because another process has locked a portion of the file.


1
Điều này thực sự không thêm bất cứ điều gì hữu ích. Trên hết, nó gọi phiên bản ANSI FormatMessage, không có lý do rõ ràng, và tự ý giới hạn ở 80 ký tự, một lần nữa, không vì lý do gì. Tôi sợ, điều này không hữu ích.
IInspectable

bạn nói đúng, tôi đã hy vọng không ai chú ý đến việc thiếu phiên bản Unicode. Tôi sẽ kiểm tra cách xác định chuỗi Unicode trong gnu và sửa đổi giải pháp của tôi. xin lỗi về sự không trung thực
Dmitry

ok phiên bản unicode đã lên. và đó không phải là lý do độc đoán; tất cả các thông báo lỗi đều dưới 80 ký tự hoặc không đáng đọc và mã lỗi quan trọng hơn thông báo lỗi. Không có thông báo lỗi tiêu chuẩn vượt quá 80 ký tự nên đó là một giả định an toàn và khi không có, nó không bị rò rỉ bộ nhớ.
Dmitry

3
"tất cả các thông báo lỗi đều dưới 80 ký tự hoặc không đáng đọc" - Điều đó sai. Thông báo lỗi cho ERROR_LOCK_VIOLATION(33) là: "Quá trình không thể truy cập tệp vì một quy trình khác đã khóa một phần của tệp." Cả hai đều rõ ràng dài hơn 80 ký tự và rất đáng đọc, nếu bạn đang cố gắng giải quyết vấn đề và tìm thấy điều này trong tệp nhật ký chẩn đoán. Câu trả lời này không thêm bất kỳ giá trị đáng kể nào vào các câu trả lời hiện có.
IInspectable

Điều này không kém phần ngây thơ. Nó chỉ đơn giản là thất bại ít thường xuyên hơn. Ngoại trừ việc thực hiện lắp ráp nằm ở kích thước bộ đệm (tuyên bố có đủ chỗ cho 200 ký tự, khi nó chỉ có đủ chỗ cho 100). Một lần nữa, điều này không thêm bất cứ điều gì đáng kể, điều đó chưa có trong bất kỳ câu trả lời nào khác. Đặc biệt, nó còn tệ hơn câu trả lời này . Xem danh sách đạn ở đó và quan sát, vấn đề mà việc triển khai đề xuất của bạn có, trên đầu trang mà tôi vừa chỉ ra.
IInspectable
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.