'python win32ui.error: BitBlt failed & CreateCompatibleBitmap failed in multithreading

I'm here to ask for help on a recent problem I encountered with my program....

I am getting this error when I try to use BitBlt and CreateCompatibleBitmap: win32ui.error: BitBlt failed & CreateCompatibleBitmap, It's worth mentioning that this happens only when my program is calling these functions from different threads, probably at the same time.

Perhaps the methods of win32gui are not multithreaded compatible?

I am currently wrapping the calls in a try-except block to prevent the program from crashing, but apparently the threads are competing to use the methods BitBlt y CreateCompatibleBitmap

I thought about ignoring the error messages and letting the threads compete this race, but it really scares me that this could result in a poor program becoming slow or breaking something by avoiding millions of these errors.

I am calling BitBlt and CreateCompatibleBitmap constantly every 50 to 100 milliseconds.

Here's an example of the function I'm calling from multiple threads:

When I do it this way I get win32gui.error CreateCompatibleBitmap failed

def get_capture_icons():
    hwndDC_icons = win32gui.GetWindowDC(game_client_hwnd)
    mfcDC_icons  = win32ui.CreateDCFromHandle(hwndDC_icons)
    saveDC_icons = mfcDC_icons.CreateCompatibleDC()
    saveBitMap_icons = win32ui.CreateBitmap()
    saveBitMap_icons.CreateCompatibleBitmap(mfcDC_icons, 106, 11)
    saveDC_icons.SelectObject(saveBitMap_icons)
    saveDC_icons.BitBlt((-16, -39), (1366, 768), mfcDC_icons, (1191, 256), win32con.SRCCOPY)
    img = Image.frombuffer('RGB', (106, 11), saveBitMap_icons.GetBitmapBits(True), 'raw', 'BGRX', 0, 1)
    mfcDC_icons.DeleteDC()
    saveDC_icons.DeleteDC()
    win32gui.ReleaseDC(game_client_hwnd, hwndDC_icons)
    win32gui.DeleteObject(saveBitMap_icons.GetHandle())
    return img

But when I save the objects in global variables I get win32gui.error BitBlt failed

def load_capturer_icons():
    global hwndDC_icons, mfcDC_icons, saveDC_icons, saveBitMap_icons
    hwndDC_icons = win32gui.GetWindowDC(game_client_hwnd)
    mfcDC_icons  = win32ui.CreateDCFromHandle(hwndDC_icons)
    saveDC_icons = mfcDC_icons.CreateCompatibleDC()
    saveBitMap_icons = win32ui.CreateBitmap()
    saveBitMap_icons.CreateCompatibleBitmap(mfcDC_icons, 106, 11)
    saveDC_icons.SelectObject(saveBitMap_icons)

def get_capture_icons():
    saveDC_icons.BitBlt((-16, -39), (1366, 768), mfcDC_icons, (1191, 256), win32con.SRCCOPY)
    return Image.frombuffer('RGB', (106, 11), saveBitMap_icons.GetBitmapBits(True), 'raw', 'BGRX', 0, 1)

Extra Info: I have 2 threads processing scripts

scripting.Thread(target=scripting.Thread.check_queue_scripts, name="Ekko").start()
scripting.Thread(target=scripting.Thread.check_queue_scripts, name="Zoe").start()

example of two scripts that make a call to get_capture_icons and generate the error explained at the beginning of this page

def callback(player, sleep):
    get_capture_icons()#call example

script = Script("callback-1", 100)
script.add_callback(callback)
script.register(__name__)

script = Script("callback-2", 100)
script.add_callback(callback)
script.register(__name__)

If I delete one of these scripts so that only 1 remains, then the error no longer appears



Solution 1:[1]

Thanks to @Tim Roberts I was able to orient myself a little more and I started an investigation in this regard, and although his answer was not accurate, he was very clear that simultaneous access to GDI objects produces race conditions, so the solution was to use threading.Lock

so now each thread executes the scripts with lock

lock.acquire()
script.execute()
lock.release()
from threading import Lock

class Thread(threading.Thread):
    def __init__(self, target: 'Callable', name: str = "", lock: threading.Lock = None):
        threading.Thread.__init__(self, target=target, args=(self, lock))
        self.running = True
        self.name = name

    def check_queue_scripts(self, lock):
        while self.running:
            script_name = Scripts.queue.get()
            script = Scripts.get_script(script_name)
            if script:
                if script.to_reload:
                    Scripts.queue.task_done()
                    Scripts.reload_script(script)
                    continue
                lock.acquire()
                script.execute()
                lock.release()
                Scripts.queue.task_done()
                Scripts.queue.put(script_name)
            sleep(1/20)


scripts_lock = Lock()
scripting.Thread(target=scripting.Thread.check_queue_scripts, name="Ekko", lock=scripts_lock).start()
scripting.Thread(target=scripting.Thread.check_queue_scripts, name="Zoe", lock=scripts_lock).start()

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1