Search code examples
pythonmemory-leakspywin32win32guiwin32-process

CreateCompatibleDC() or DeleteDC() fail in continues loop in Python - possible memory leak?


I am feeding an opencv window in a loop with this specific window screen capture routine below.

PROBLEM: after hundreds of cycles in the loop, it suddenly fail at either one of the two FAIL POINTS marked below in the code.

I am suspecting possible memory leak, but if I am not mistaken, I do delete and release what's required as well as I (re)select object before I delete it.

(The reason I am using this method, because it is important for me to be able to capture the specific window even if it is inactive and in the background and I did not found any other module/method actually works.)

What am I overlooking?

import win32gui
import win32ui
from PIL import Image
import numpy as np
import cv2

while True:

        target_window = win32gui.FindWindow(None, ("Analytics dashboard - Google Chrome"))       
        
        hwndDC = win32gui.GetWindowDC(target_window) 
        mfcDC  = win32ui.CreateDCFromHandle(hwndDC)  

        saveDC = mfcDC.CreateCompatibleDC()  #### <-- RANDOM FAIL POINT 1: win32ui.error: CreateCompatibleDC failed
                
        saveBitMap = win32ui.CreateBitmap()
        saveBitMap.CreateCompatibleBitmap(mfcDC, screen_width, screen_height)
        saveDC.SelectObject(saveBitMap)    
        result = windll.user32.PrintWindow(target_window, saveDC.GetSafeHdc(), 3)
        bmpinfo = saveBitMap.GetInfo()
        bmpstr = saveBitMap.GetBitmapBits(True)
        
        screen_image = Image.frombuffer('RGB', (bmpinfo['bmWidth'], bmpinfo['bmHeight']), bmpstr, 'raw', 'BGRX', 0, 1)        

        mfcDC.DeleteDC()  #### <-- RANDOM FAIL POINT 2: win32ui.error: DeleteDC failed

        saveDC.DeleteDC()               
        win32gui.DeleteObject(saveBitMap.GetHandle())        
        win32gui.ReleaseDC(target_window, hwndDC) 

        image = cv2.cvtColor(np.array(screen_image), cv2.IMREAD_ANYCOLOR)

Solution

  • I tried the above code with ctypes.windll.user32.PrintWindow and there were no GDI leaks. PrintWindow's third argument should be PW_CLIENTONLY (1), or there is the undocumented PW_RENDERFULLCONTENT (2) option. Undocumented code is not reliable. I don't know what the constant (3) refers to.

    If Chrome is the top window you should just take screen shot of desktop. This would be compliant.

    It might help if you remove some of the code outside the loop, it will be more efficient at least.

    import ctypes
    import win32gui 
    import win32ui
    import win32con
    from PIL import Image
    
    hdesktop = win32gui.GetDesktopWindow()
    (l, r, width, height) = win32gui.GetClientRect(hdesktop)
    hdc = win32gui.GetWindowDC(hdesktop)
    dc  = win32ui.CreateDCFromHandle(hdc)
    memdc = dc.CreateCompatibleDC()
    bitmap = win32ui.CreateBitmap()
    bitmap.CreateCompatibleBitmap(dc, width, height)
    memdc.SelectObject(bitmap)
    
    while True:
        hwnd = win32gui.FindWindow("Chrome_WidgetWin_1", None)
        if hwnd == 0:
            break
        result = ctypes.windll.user32.PrintWindow(hwnd, memdc.GetSafeHdc(), 2)
        if result == 1:
            bytes = bitmap.GetBitmapBits(True)
            img = Image.frombuffer('RGB', (width, height), bytes, 'raw', 'BGRX', 0, 1)
            img.save("file.bmp")
        #break
        
    dc.DeleteDC()
    memdc.DeleteDC()
    win32gui.DeleteObject(bitmap.GetHandle())
    win32gui.ReleaseDC(hwnd, hdc)
    

    You can also add ctypes.windll.shcore.SetProcessDpiAwareness(2) on top