使用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
相关产品推荐
相关产品推荐

