CreateCompatibleDC fails after calling it exactly 4,984 times

断了今生、忘了曾经 提交于 2019-12-13 19:28:03

问题


I've encountered a strange bug in my program. It's a little odd, as it occurs on exactly the 4984th call to the function. I've been tweaking this all day, and without fail, that's the number at which it fails.

The code in question is a small convenience function which creates and returns a DC and Bitmap. The context of this little function is that it's a piece in my stab at a screen recorder, so it's getting called tons and tons of times.

When I first noticed the error, after some sleuthing around, I found this very similar Stackoverflow question, so the code below is modeled after the answer in that thread. However, even after following the suggested deletion and releasing pattern, the problem remains for me right on that 4984th iteration.

This is the specific failure point of the program:

def _createDcAndBitmap(self, size, input_bitmap=None):
    hwnd = win32gui.GetDesktopWindow()
    zhwndDevice = win32gui.GetWindowDC(hwnd)
    zmfcDC  = win32ui.CreateDCFromHandle(zhwndDevice)
    zsaveDC = zmfcDC.CreateCompatibleDC()
    zsaveBitMap = win32ui.CreateBitmap()
    zsaveBitMap.CreateCompatibleBitmap(zmfcDC, *size)
    hOldBmp = zsaveDC.SelectObject(zsaveBitMap)
    return zsaveDC, zsaveBitMap, hOldBmp, hwnd

The error is always throw from the line:

zsaveBitMap.CreateCompatibleBitmap(zmfcDC, *size)  

With the error reported by Python as:

error: CreateCompatibleDC failed  

Calling FormatMessage from the win32api gives further information:

Invalid device context (DC) handle.  

The Full Code:

class Bitmap(object):
    _sourceDC, _sourceBitmap, hOldBmp, hwnd = self._bytesToDcAndBitmap(bytestring, sourceSize)
    _bytes, _size = self._scaleBitmap(_sourceDC, _sourceBitmap, hOldBmp, hwnd,     sourceSize)


def _scaleBitmap(self, sourceDC, sourceBitmap, sourceHOldBmp, sourceHwnd, sourceSize):
    '''
    Resizes the current bitmap down to a target size 
    of (X, 540), where the X is varied depending on the 
    aspect ratio of the input bitmap 
    '''
    target_size = self._getTargetSize(sourceSize)
    destDC, destBitmap, hOldBmp, hwnd = self._createDcAndBitmap(target_size)

    win32gui.SetStretchBltMode(destDC.GetHandleAttrib(), 4)
    win32gui.StretchBlt(pywintypes.HANDLE(destDC.GetHandleAttrib()), 0,0,target_size[0], target_size[1],  # @UndefinedVariable HANDLE -- PyDev is dumb
                                                                                sourceDC.GetHandleAttrib(), 0,0, sourceSize[0], sourceSize[1], win32con.SRCCOPY)

    new_bytestring = destBitmap.GetBitmapBits(True)
    new_size = self._bitmapSize(destBitmap)

    self._deleteDCBitmapOldBmpAndHwmn(sourceDC, sourceBitmap, sourceHOldBmp, sourceHwnd)
    self._deleteDCBitmapOldBmpAndHwmn(destDC, destBitmap, hOldBmp, hwnd)


def _bytesToDcAndBitmap(self, bytestring, sourceSize):
    a = (ctypes.c_int * (sourceSize[0]*sourceSize[1]))()
    ctypes.memmove(a, bytestring, len(bytestring))

    hwnd = win32gui.GetDesktopWindow()
    zhwndDevice = win32gui.GetWindowDC(hwnd)
    zmfcDC  = win32ui.CreateDCFromHandle(zhwndDevice)
    zsaveDC = zmfcDC.CreateCompatibleDC()
    zsaveBitMap = win32ui.CreateBitmap()
    zsaveBitMap.CreateCompatibleBitmap(zmfcDC, sourceSize[0], sourceSize[1])
    hOldBmp = zsaveDC.SelectObject(zsaveBitMap)

    ctypes.windll.gdi32.SetBitmapBits(zsaveBitMap.GetHandle(), len(bytestring), ctypes.byref(a))

    return zsaveDC, zsaveBitMap, hOldBmp, hwnd  



def _createDcAndBitmap(self, size, input_bitmap=None):
    hwnd = win32gui.GetDesktopWindow()
    zhwndDevice = win32gui.GetWindowDC(hwnd)
    zmfcDC  = win32ui.CreateDCFromHandle(zhwndDevice)
    zsaveDC = zmfcDC.CreateCompatibleDC()
    zsaveBitMap = win32ui.CreateBitmap()
    zsaveBitMap.CreateCompatibleBitmap(zmfcDC, *size)
    hOldBmp = zsaveDC.SelectObject(zsaveBitMap)
    return zsaveDC, zsaveBitMap, hOldBmp, hwnd


def _deleteDCBitmapOldBmpAndHwmn(self, dc, bitmap, old_bitmap, hwnd):
    win32gui.SelectObject(dc.GetHandleAttrib(), old_bitmap.GetHandle())
    win32gui.DeleteDC(dc.GetHandleAttrib())
    win32gui.DeleteObject(bitmap.GetHandle())

    win32gui.ReleaseDC(win32gui.GetDesktopWindow(), hwnd)   

The code is a little peculiar, as it's running on the 'exit' end of a pipe. So it's job is reconstructing a serialized byte string (gotten from GetBitmapBits()) back into a Bitmap, scaling it, then going back to a byte string. Doing it this way is about a solid order of magnitude faster than using higher level Python libraries :)

So, I'm guessing this is due to a memory leak somewhere, but as far as I can tell, I'm closing everything down correctly. And yet, it still fails right around the 5000th call.

Am I missing a leak somewhere?

来源:https://stackoverflow.com/questions/21100028/createcompatibledc-fails-after-calling-it-exactly-4-984-times

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!