Python循环中CreateCompatibleDC/DeleteDC失败,是否存在内存泄漏?
后台窗口截图循环报错解决方案
问题根因
你遇到的随机报错本质是GDI资源泄漏,Windows系统对单进程的GDI句柄数量有上限限制(默认10000个),当泄漏的句柄累积到上限后,DC、位图创建等操作就会随机失败。你当前的实现遗漏了以下关键注意点:
- 资源释放顺序错误:
saveDC是基于mfcDC创建的兼容DC,必须先释放saveDC再释放mfcDC,你当前的释放顺序相反,会导致mfcDC引用计数异常无法正常释放。 - 未还原DC的原始选中对象:GDI规范要求,向DC中选入自定义位图后,操作完成必须选回DC默认的原始对象,否则被选中的位图无法被正常删除,直接造成资源泄漏。
- 缺少前置有效性校验:每次循环调用
FindWindow获取的窗口句柄可能为空(比如窗口被关闭、最小化、进程无响应、标题变动),未校验直接调用后续API会生成无效DC,释放逻辑也会失败。 - 无异常兜底机制:循环中没有异常捕获逻辑,任意一步操作失败后,后续的资源释放代码不会执行,每次失败都会泄漏一批GDI句柄,快速累积到上限。
- 未动态获取窗口尺寸:写死的
screen_width、screen_height和窗口实际尺寸不符时,会导致位图创建、截图操作异常,间接引发资源泄漏。 - 颜色转换参数错误:原代码中
cv2.cvtColor使用了读取文件专用的IMREAD_ANYCOLOR参数,会导致图像处理异常,甚至触发内存泄漏。
修正后代码
import win32gui import win32ui from PIL import Image import numpy as np import cv2 from ctypes import windll while True: target_window = win32gui.FindWindow(None, "Analytics dashboard - Google Chrome") # 校验窗口句柄有效性 if not target_window: cv2.waitKey(100) continue hwndDC = None mfcDC = None saveDC = None saveBitMap = None old_obj = None try: # 动态获取窗口尺寸 left, top, right, bottom = win32gui.GetWindowRect(target_window) screen_width = right - left screen_height = bottom - top hwndDC = win32gui.GetWindowDC(target_window) mfcDC = win32ui.CreateDCFromHandle(hwndDC) saveDC = mfcDC.CreateCompatibleDC() saveBitMap = win32ui.CreateBitmap() saveBitMap.CreateCompatibleBitmap(mfcDC, screen_width, screen_height) # 保存DC原来的选中对象 old_obj = saveDC.SelectObject(saveBitMap) # 校验PrintWindow调用结果 result = windll.user32.PrintWindow(target_window, saveDC.GetSafeHdc(), 3) if not result: continue bmpinfo = saveBitMap.GetInfo() bmpstr = saveBitMap.GetBitmapBits(True) screen_image = Image.frombuffer('RGB', (bmpinfo['bmWidth'], bmpinfo['bmHeight']), bmpstr, 'raw', 'BGRX', 0, 1) image = cv2.cvtColor(np.array(screen_image), cv2.COLOR_BGR2RGB) # 此处添加opencv窗口渲染逻辑 # cv2.imshow("screen", image) # if cv2.waitKey(1) & 0xFF == ord('q'): # break except Exception as e: print(f"截图出错: {str(e)}") cv2.waitKey(100) finally: # 按顺序释放资源,先还原DC选中对象 if saveDC and old_obj: try: saveDC.SelectObject(old_obj) except: pass # 先释放子DC if saveDC: try: saveDC.DeleteDC() except: pass # 再释放父DC if mfcDC: try: mfcDC.DeleteDC() except: pass # 释放位图 if saveBitMap: try: win32gui.DeleteObject(saveBitMap.GetHandle()) except: pass # 释放窗口DC if target_window and hwndDC: try: win32gui.ReleaseDC(target_window, hwndDC) except: pass cv2.destroyAllWindows()
内容的提问来源于stack exchange,提问作者D.Moore
相关产品推荐
相关产品推荐

