You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用GDI截图时createDCFromHandle/createCompatibleDC内存泄漏排查

解决GDI屏幕捕获迭代80次后创建DC失败的内存泄漏问题

我帮你梳理下这个问题的核心原因和解决思路——你遇到的其实是GDI句柄耗尽的问题,Windows对每个进程的GDI句柄数量有上限,你的代码每次迭代都泄漏几个资源,80次后就触碰到临界值了。从你的代码来看,还有几处资源释放的漏洞,我逐个给你分析:

1. 修复getfunc中的资源释放逻辑

你的代码里有两个关键的泄漏点:

  • mem_dc重复创建未释放:每次调用getfunc都会创建新的self.mem_dc,但之前的mem_dc没有被删除就直接覆盖,导致旧句柄永久泄漏。
  • 位图对象的SelectObject未恢复原句柄:当你调用self.mem_dc.SelectObject(screenshot)时,DC里原来的占位位图会被返回,这个旧位图句柄也需要删除,否则会泄漏。

修改后的getfunc应该这样:

def getfunc(self):
    # 先释放上一次的mem_dc(如果存在)
    if hasattr(self, 'mem_dc') and self.mem_dc:
        self.mem_dc.DeleteDC()
        self.mem_dc = None

    # grab a handle to the main desktop window
    hdesktop = win32gui.GetDesktopWindow()
    # create a device context
    desktop_dc = win32gui.GetWindowDC(hdesktop)
    img_dc = win32ui.CreateDCFromHandle(desktop_dc)
    # create a memory based device context
    self.mem_dc = img_dc.CreateCompatibleDC()
    # create a bitmap object
    screenshot = win32ui.CreateBitmap()
    screenshot.CreateCompatibleBitmap(img_dc, width, height)
    
    # 保存旧的位图句柄,后续要恢复并删除
    old_bitmap = self.mem_dc.SelectObject(screenshot)
    # copy the screen into our memory device context
    try:
        self.mem_dc.BitBlt((destUpLeftX, destUpLeftY), (width, height), img_dc, (srcUpLeftX, srcUpLeftY), win32con.SRCCOPY)
    except Exception as e:
        logger.debug(f"BitBlt failed: {str(e)}")
    finally:
        # 恢复DC的原位图句柄
        self.mem_dc.SelectObject(old_bitmap)
        # 释放位图资源
        win32gui.DeleteObject(screenshot.GetHandle())
        win32gui.DeleteObject(old_bitmap)
        # 释放DC资源
        img_dc.DeleteDC()
        win32gui.ReleaseDC(hdesktop, desktop_dc)

2. 完善delete函数的鲁棒性

确保delete函数不会因为重复调用或者mem_dc已被释放而报错,同时彻底清理资源:

def delete(self):
    if hasattr(self, 'mem_dc') and self.mem_dc:
        self.mem_dc.DeleteDC()
        self.mem_dc = None

3. 用工具验证泄漏是否解决

你可以用Windows自带的工具来确认资源是否不再泄漏:

  • 打开任务管理器,切换到「详细信息」标签,右键表头添加「GDI对象」列。
  • 运行你的程序,观察每次迭代后GDI对象的数量,如果不再持续增长,说明泄漏已经修复。
  • 如果还是有增长,可以用Process Explorer(微软Sysinternals工具)查看具体的GDI句柄类型,定位剩余的泄漏点。

4. 可选:用上下文管理器封装GDI资源

为了避免手动释放出错,你可以把GDI资源的创建和释放用上下文管理器封装,确保异常情况下也能正确释放:

from contextlib import contextmanager

@contextmanager
def gdi_dc_manager(dc):
    try:
        yield dc
    finally:
        dc.DeleteDC()

@contextmanager
def gdi_bitmap_manager(bitmap):
    try:
        yield bitmap
    finally:
        win32gui.DeleteObject(bitmap.GetHandle())

使用时就可以用with语句自动管理资源,减少手动释放的失误。


内容的提问来源于stack exchange,提问作者Rahul

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:17:47