【问题标题】:Brushes deleteObject(hBrush) in c++在 C++ 中画笔 deleteObject(hBrush)
【发布时间】:2021-08-15 03:38:13
【问题描述】:

我有点困惑。一般来说,新的Brushes是不是应该在使用后删除呢? 每次运行以下代码时,我都担心内存泄漏:

    case WM_CTLCOLORLISTBOX:
{
    
    HBRUSH hbrush;
    
    hbrush = CreateSolidBrush(RGB(255, 255, 0));    // Green BackColor in ComboBox List area

    HWND hTest = (HWND)lParam;
    COMBOBOXINFO ci = { sizeof(COMBOBOXINFO) };

    GetComboBoxInfo(hComboMode, &ci);       // CBS_DROPDOWNLIST 

    if (hTest == ci.hwndItem || hTest == ci.hwndList)
    {
        HDC hdc = (HDC)wParam;

        SetTextColor(hdc, RGB(0, 0, 0));    // Black Text in ComboBox List area
        
        SetBkMode(hdc, TRANSPARENT);
        return (INT_PTR)hbrush;
    }

    // DeleteObject(hbrush);
}

有两件事让我感到困惑。首先,如果控制权进入 if 语句,那么据我所知,只要 return 语句执行,就根本没有机会执行 deleteObject(hbrush) 吗? case 语句是在那个时候退出的吗?那么,如果是这种情况,那么我将永远无法删除画笔,因为我需要将它作为返回语句的一部分传回?顺便说一句,这段代码是我在 SO 上找到的不同代码段的“大杂烩”。我需要补充一点,在原始代码中,第一行是“static HBRUSH hBrush”。为了讨论的目的,我特意删除了“静态”这个词,在这里。请原谅我的无知,但我所知道的是“静态”将在堆上创建变量 - 而不是在堆栈上,随后执行代码将一遍又一遍地发现处于相同状态的变量;它不是像我现在那样每次都动态创建的。

我感到困惑的是,如果变量被设为“静态”,那么我是否还需要在某个时候“删除”它?既然是静态创建的,就不会有“内存泄露”?

很抱歉给您带来了困惑。我很久没有用 C++ 编程了。我正在慢慢地学习绳索。提前感谢您的帮助和指导。

【问题讨论】:

  • 需要在WM_CREATEWM_INITDIALOG 上调用一次CreateSolidBrush 并将结果保存在与您的窗口关联的类对象中。并在WM_DESTROY上致电DeleteObject
  • @RbMm:所以我不能“创建”新的 HBRUSH 对象,最好是在 WM_CREATE 上创建它们并在 WM_DESTROY 上删除它们?
  • 当然您可以在任何地点/时间创建新的 HBRUSH 对象。另一个问题 - 这会很好并且有意义。通常在窗口创建和保存/使用时创建“当前”画笔。您可以根据某些事件重新创建画笔(删除旧的/创建并保存新的)。例如 - 当用户决定更改它时。
  • @RbMm:我在 WM_CREATE 上尝试了 CreateSolidBrush 并得到以下结果:“错误 C2360 的 'CustomBrush' 初始化被 'case' 标签跳过”?我决定将声明移至全局范围...
  • 将声明移动到全局范围 - 错误。 'CustomBrush' 的错误 C2360 初始化被 'case' 标签跳过 仅表示 c++ 代码中的错误

标签: c++ windows winapi memory-leaks combobox


【解决方案1】:

WM_CTLCOLORLISTBOX 消息旨在泄漏。系统期望实现产生一个 GDI 对象并将其分发出去。当系统完成使用该资源时,没有回调通知该资源的所有者。

您无法选择WM_CTLCOLORLISTBOX 消息处理程序是否泄漏。它将,总是,按设计。您唯一的选择是是否要防止该泄漏因资源耗尽而导致问题1

问题中提供的实现决定使泄漏受到伤害。它为每个发送到对话过程的WM_CTLCOLORLISTBOX 消息创建一个新的画笔对象,而无需清理。系统最终会耗尽 GDI 资源。

有很多方法可以解决这个问题:

委派责任

解决此问题的最简单方法是将清理工作委托给其他人。系统维护一组经常使用的 GDI 对象,可通过GetStockObject 访问。由于“没有必要删除股票对象”除了返回股票对象之外,您没有任何责任。

但是...只有黑色、灰色和白色画笔作为库存对象提供。如果我需要绿色刷子怎么办?幸运的是,该系统涵盖了您。您只需将两部分放在一起:返回 DC_BRUSH 并通过 SetDCBrushColor 请求所需的颜色:

    if (hTest == ci.hwndItem || hTest == ci.hwndList)
    {
        auto const hdc{ reinterpret_cast<HDC>(wParam) };

        SetTextColor(hdc, RGB(0, 0, 0));    // Black Text in ComboBox List area
        SetBkMode(hdc, TRANSPARENT);

        // Set solid brush color
        ::SetDCBrushColor(hdc, RGB(0, 255, 0));
        // Request the system to use the DC_BRUSH
        return reinterpret_cast<INT_PTR>(::GetStockObject(DC_BRUSH));
    }

设置泄漏计数和/或持续时间的上限

当系统已经提供了您需要的一切(如纯色画笔)时,之前的解决方案非常适合。如果您确实需要更复杂的东西(例如图案画笔),那么创建和销毁对象的责任又将由您来承担。

同样,问题中已经提到了一个简单的解决方案:使用带有static storage duration 的对象。具有静态存储持续时间的对象最多初始化一次,非常适合这里。虽然它不能防止泄漏,但它会将泄漏对象的数量限制为不超过 1:

case WM_CTLCOLORLISTBOX:
{
    // Make sure this gets created at most once
    static auto const brush{::CreateSolidBrush(RGB(0, 255, 0))};

    // ...
    
    return reinterpret_cast<INT_PTR>(brush);
}

最终效果是,无论窗口过程收到多少次WM_CTLCOLORLISTBOX 请求2brush 对象最多被创建一次。这会将泄漏计数的上限设为 1,从而防止由于无限资源消耗而导致资源耗尽。

同样,可以限制泄漏的持续时间。虽然您不知道系统使用画笔完成的确切时间点,但您可以确定在控件的父窗口被销毁时将其丢弃是安全的。父窗口收到WM_NCDESTROY 消息“在子窗口被销毁后”

实施这个方案相当复杂。它需要相当多的设置,例如将数据与窗口类实例或窗口类相关联,以及状态管理。从广义上讲,该解决方案按需分配资源(例如,第一次WM_CTLCOLORLISTBOX 消息到达时),并在最终客户端被销毁时释放它们。让这一切充实起来需要一个全新的问答。


诊断

GDI 资源有限。保守是至关重要的,在你不保守的时候学习是必要的。验证 GDI 资源使用不会随时间增加的快速方法是使用任务管理器。在详细信息选项卡上,右键单击列表视图的标题控件,选择“选择列”并勾选“GDI对象”选项.

如果您确实遇到问题并想查明根本原因,操作系统提供了GetGuiResources API,允许您观察代码中不同位置的资源计数。


1系统对GDI objects全局(每个用户会话)限制非常低。
2 这是正确的,即使窗口过程被多个窗口实例使用。

【讨论】:

  • 非常感谢您花时间解释。我对我应该如何“做到这一点”一无所知?有人建议我正确处理该问题的一种方法是通过 WM_DESTROY 事件,使用 DeleteObject(myCustomBrushes) 指令:这也是可接受的解决方案吗?我将需要在我的应用程序中创建很多自定义画笔,如果这是在屏幕上“绘制”的唯一方法。看我最近的帖子......你的观点很好,亲爱的朋友。
猜你喜欢
  • 2014-04-09
  • 2010-09-27
  • 1970-01-01
  • 2011-04-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多