WM_CTLCOLORSTATIC中创建的自定义画笔如何正确释放以避免资源泄漏?
这个问题我做Win32原生界面时也踩过一模一样的坑!你现在的代码逻辑最大的问题是:每次WM_CTLCOLORSTATIC消息触发时,你都调用CreateSolidBrush生成新的画笔,然后直接返回它——Windows会用这个画笔完成绘制,但不会帮你自动销毁它。而WM_CTLCOLORSTATIC会被频繁触发(控件重绘、窗口刷新、甚至鼠标移过时都可能发这个消息),日积月累就会造成GDI资源泄漏,严重的话还会导致界面绘制异常。
正确的解决思路:把画笔的创建和释放从消息处理中抽离
要解决这个问题,核心是提前创建好自定义画笔,重复使用同一个画笔句柄,最后在窗口销毁时统一释放,而不是每次消息都新建。具体步骤如下:
1. 先声明变量保存画笔句柄
你需要在窗口的上下文里保存自定义画笔的句柄,比如用全局变量(单窗口场景下)或者把它存在窗口的附加数据结构中:
// 可以放在窗口回调函数外作为全局变量,或者封装到窗口的自定义数据里 HBRUSH hTextBoxBrush = NULL; HBRUSH hInfoBoxBrush = NULL;
2. 在窗口初始化时创建画笔
在窗口创建的WM_CREATE消息中,一次性创建好需要的自定义画笔(注意要确保colorbkgnd01此时已经有正确的初始值):
case WM_CREATE: // 创建文本框的自定义背景画笔 hTextBoxBrush = CreateSolidBrush(0xBEBEBE); // 创建infoBox01的自定义背景画笔 hInfoBoxBrush = CreateSolidBrush(colorbkgnd01); break;
3. 修改WM_CTLCOLORSTATIC的处理逻辑,返回预先创建的画笔
现在不需要每次消息触发都新建画笔,直接返回之前创建好的句柄即可:
case WM_CTLCOLORSTATIC: { HDC hdc = (HDC)wParam; HWND hwndStatic = (HWND)lParam; if (hwndStatic == *phwndTextBox) { SetTextColor(hdc, 0x000000); SetBkColor(hdc, 0xBEBEBE); // 返回预先创建的画笔,而非每次新建 return (LRESULT)hTextBoxBrush; } if (hwndStatic == *phwndinfoBox01) { SetTextColor(hdc, 0x000000); SetBkColor(hdc, colorbkgnd01); // 同样返回预先创建的画笔 return (LRESULT)hInfoBoxBrush; } } break;
4. 在窗口销毁时释放画笔资源
最后,在窗口收到WM_DESTROY消息时,一定要用DeleteObject释放这些自定义画笔,彻底避免泄漏:
case WM_DESTROY: // 释放文本框的画笔 if (hTextBoxBrush != NULL) { DeleteObject(hTextBoxBrush); hTextBoxBrush = NULL; } // 释放infoBox01的画笔 if (hInfoBoxBrush != NULL) { DeleteObject(hInfoBoxBrush); hInfoBoxBrush = NULL; } PostQuitMessage(0); break;
额外补充:动态修改画笔颜色的情况
如果你的colorbkgnd01是动态变化的(比如用户可以在运行时修改背景色),那需要在颜色变更时,先销毁旧的画笔,再创建新的,同时触发控件重绘让新颜色生效:
// 当colorbkgnd01需要更新时的代码示例 if (hInfoBoxBrush != NULL) { // 先销毁旧画笔,避免泄漏 DeleteObject(hInfoBoxBrush); } // 创建新颜色的画笔 hInfoBoxBrush = CreateSolidBrush(newColorValue); // 触发infoBox01控件重绘,让新颜色立即生效 InvalidateRect(*phwndinfoBox01, NULL, TRUE);
为什么GetStockObject不会泄漏?
你提到用GetStockObject(比如LTGRAY_BRUSH)不会泄漏,这是因为库存画笔是系统全局管理的资源,不用我们手动调用DeleteObject释放,Windows会负责它们的生命周期。但库存对象的颜色是固定的,没法满足自定义颜色的需求,所以需要自定义颜色时,还是得用上面的方法。
验证泄漏是否解决
你可以用Windows自带的任务管理器验证:打开任务管理器→找到你的进程→右键→「转到详细信息」→右键列→「选择列」→勾选「GDI对象」。如果修改后的代码运行时,GDI对象数量不再持续增长,就说明泄漏问题解决了。
备注:内容来源于stack exchange,提问作者PeterS

