【问题标题】:CreateFont, DeleteObject and GDI handle/memory leakCreateFont、DeleteObject 和 GDI 句柄/内存泄漏
【发布时间】:2020-12-20 05:52:01
【问题描述】:

我正在查看 https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-createfontaCreateFont API 示例

它清楚地表明,在使用CreateFont 创建字体后,它应该被DeleteObject 调用销毁。 DeleteObject(hFont); 只被调用一次。 CreateFont 被调用了 3 次。这是 MS 文档中的错误吗?不应该通过SelectObject检索旧字体来存储旧字体并在使用新字体后重新设置吗?

【问题讨论】:

  • 作为一般评论,不要相信 MS 文档页面中的代码 sn-ps 不仅仅如此 - 代码 sn-ps。它们当然不是真实的,甚至不是“示例”代码——无论如何,您可能不希望在文档页面中出现生产级代码。对于实际示例,请参阅他们的 repo 和其中的链接。
  • 今年早些时候asked the same question 的其他人。他们试图修复文档,但只会让情况变得更糟。值得庆幸的是,拉取请求从未被合并到官方文档中。
  • 看起来很糟糕。我刚刚阅读了该拉取请求上的 cmets 线程...
  • 您好,如果任何答案对您有所帮助,请随时标记它以帮助遇到相同问题的人,如果您有任何问题,请告诉我。谢谢。

标签: c++ winapi gdi


【解决方案1】:

是的,有两个创建的字体对象泄露了。

请注意,MS 示例代码在错误处理和对象清理方面通常很差(它们通常专注于演示示例的核心内容 - 这里是 CreateFont 调用 - 而忽略或最小化这些问题)。

【讨论】:

  • 其实这三个都泄露了,因为在设备上下文中原始字体没有恢复,引用Raymond Chen的What are the dire consequences of not selecting objects out of my DC?:“如果你选择一个字体进入DC,还有人尝试销毁字体,DeleteObject 调用将失败,您最终会泄漏字体"。
  • @dxiv 有趣的文章,谢谢。这与 Win9X Oses 相关,当不选择 GDI 对象时会消耗所有系统 GDI 资源,尤其是句柄。在基于 NT 的系统上似乎不存在泄漏句柄的问题。 “泄漏”内存(我说的是未由 SelectObject 恢复的 GDI 对象)将一直坐在那里,直到进程终止。
  • @dgrandm 不,唯一的区别是在基于 NT 的系统中,进程退出时句柄会关闭。但是 GDI 句柄 (64K) 仍然存在系统范围的限制,因此如果您的进程是一个长期运行的进程,那么任何此类累积泄漏最终都会咬到您。
  • @dxiv 因此可以安全地假设在 NT 系统上,如果我不恢复分配给 DC 的原始 GDI 句柄,它不会在系统范围内泄漏,因为它将被进程关闭?我依稀记得在 9X 上是进程关闭后系统范围的泄漏。
  • @dgrandm 是的,从 NT 早期开始就是 safe to assume
【解决方案2】:

文档中的示例确实导致了字体对象的泄漏

我构建了一个示例如下:

#include <Windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(_In_  HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_  LPSTR szCmdLine, _In_  int iCmdShow)
{
    static TCHAR szAppName[] = TEXT("hello windows");
    HWND hwnd;
    MSG msg;
    WNDCLASS wndclass;
    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc = WndProc;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = hInstance;
    wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndclass.lpszMenuName = NULL;
    wndclass.lpszClassName = szAppName;
    if (!RegisterClass(&wndclass))
    {
        MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR);
    }

    hwnd = CreateWindow(szAppName,
        TEXT("the hello program"),
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        NULL,
        NULL,
        hInstance,
        NULL);
    ShowWindow(hwnd, iCmdShow);
    UpdateWindow(hwnd);
    while (GetMessageW(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }
    return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    int wmId, wmEvent;
    PAINTSTRUCT ps;
    HDC hdc;
    switch (message)
    {


    case WM_PAINT:
    {
        RECT rect;
        HBRUSH hBrush;
        HFONT hFont;
        hdc = BeginPaint(hWnd, &ps);


        //Logical units are device dependent pixels, so this will create a handle to a logical font that is 48 pixels in height.
        //The width, when set to 0, will cause the font mapper to choose the closest matching value.
        //The font face name will be Impact.
        hFont = CreateFont(48, 0, 0, 0, FW_DONTCARE, FALSE, TRUE, FALSE, DEFAULT_CHARSET, OUT_OUTLINE_PRECIS,
            CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, VARIABLE_PITCH, TEXT("Impact"));
        SelectObject(hdc, hFont);

        //Sets the coordinates for the rectangle in which the text is to be formatted.
        SetRect(&rect, 100, 100, 700, 200);
        SetTextColor(hdc, RGB(255, 0, 0));
        DrawText(hdc, TEXT("Drawing Text with Impact"), -1, &rect, DT_NOCLIP);

        //DeleteObject(hFont);
        //Logical units are device dependent pixels, so this will create a handle to a logical font that is 36 pixels in height.
        //The width, when set to 20, will cause the font mapper to choose a font which, in this case, is stretched.
        //The font face name will be Times New Roman.  This time nEscapement is at -300 tenths of a degree (-30 degrees)
        hFont = CreateFont(36, 20, -300, 0, FW_DONTCARE, FALSE, TRUE, FALSE, DEFAULT_CHARSET, OUT_OUTLINE_PRECIS,
            CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, VARIABLE_PITCH, TEXT("Times New Roman"));
        SelectObject(hdc, hFont);

        //Sets the coordinates for the rectangle in which the text is to be formatted.
        SetRect(&rect, 100, 200, 900, 800);
        SetTextColor(hdc, RGB(0, 128, 0));
        DrawText(hdc, TEXT("Drawing Text with Times New Roman"), -1, &rect, DT_NOCLIP);

        //DeleteObject(hFont);
        //Logical units are device dependent pixels, so this will create a handle to a logical font that is 36 pixels in height.
        //The width, when set to 10, will cause the font mapper to choose a font which, in this case, is compressed. 
        //The font face name will be Arial. This time nEscapement is at 250 tenths of a degree (25 degrees)
        hFont = CreateFont(36, 10, 250, 0, FW_DONTCARE, FALSE, TRUE, FALSE, DEFAULT_CHARSET, OUT_OUTLINE_PRECIS,
            CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, VARIABLE_PITCH, TEXT("Arial"));
        SelectObject(hdc, hFont);

        //Sets the coordinates for the rectangle in which the text is to be formatted.
        SetRect(&rect, 500, 200, 1400, 600);
        SetTextColor(hdc, RGB(0, 0, 255));
        DrawText(hdc, TEXT("Drawing Text with Arial"), -1, &rect, DT_NOCLIP);
        DeleteObject(hFont);
        EndPaint(hWnd, &ps);
        break;
    }
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

运行示例后启动任务管理器,可以详细查看:

然后触发WM_PAINT消息:

我们可以发现它的GDI Objects增加了2,每次触发都会增加,所以这个例子会造成对象泄漏。

当我们每次使用后调用DeleteObject(hFont);(在我的示例的第75行和第88行),并重复上述步骤,我们会发现GDI Objects不会增加,从而解决了对象泄漏的问题。

【讨论】:

    猜你喜欢
    • 2013-06-10
    • 2012-02-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多