创建太多实例时,窗口过程覆盖会导致崩溃而不报错

问题描述 投票:0回答:1

我正在制作一个使用 SDL / Pygame 来显示图形的应用程序。我已经覆盖了窗口过程,以防调整大小以触发功能并使应用程序运行更流畅(参见这个答案)。

但是我注意到,当达到一定数量的类实例时,它会使应用程序崩溃,并且日志中没有任何错误。我检查了 Windows 日志,但找不到任何内容。我发现罪魁祸首是窗口过程被覆盖。

在下面找到该问题的模拟:

  • 函数
    windows_resize_procedure
    (灵感来自这个答案当用户调整窗口大小时触发
    App.resize
    App.draw
  • [Element() for _ in range(1000)]
    人为地创建大量
    Element
    实例。在我的真实程序中,我只有大约 50 个,但我认为它们“更大”(更多属性和方法)。在我的计算机上,1000 足以使程序在几秒钟内崩溃,但您可能希望根据您的计算机增加此数字。
import pygame,sys,platform

def windows_resize_procedure(hwnd,draw_func,resize_func,screen):
    try:
        import ctypes
        from ctypes import wintypes

        user32 = ctypes.windll.user32

        WNDPROC = ctypes.WINFUNCTYPE(
            ctypes.c_long, 
            wintypes.HWND, 
            ctypes.c_uint, 
            ctypes.POINTER(wintypes.WPARAM),
            ctypes.POINTER(wintypes.LPARAM))
        WM_SIZE = 0x0005
        RDW_INVALIDATE = 0x0001
        RDW_ERASE = 0x0004
        GWL_WNDPROC = -4

        old_window_proc = user32.GetWindowLongPtrA(
            user32.GetForegroundWindow(),
            GWL_WNDPROC
        )

        def new_window_proc(hwnd, msg, wparam, lparam):
            if msg == WM_SIZE:
                resize_func(screen.get_size())
                draw_func()
                user32.RedrawWindow(hwnd, None, None, RDW_INVALIDATE | RDW_ERASE)
            return user32.CallWindowProcA(old_window_proc, hwnd, msg, wparam, lparam)


        new_window_proc_cb = WNDPROC(new_window_proc)

        user32.SetWindowLongPtrA(
            user32.GetForegroundWindow(), 
            GWL_WNDPROC, 
            ctypes.cast(new_window_proc_cb, ctypes.POINTER(ctypes.c_long))
        )
    except Exception as e:
        print(e)

class Element:
    def __init__(self):
        self.foo = 'foo'

class App:
    def __init__(self):
        pygame.init()
        self.screen = pygame.display.set_mode((200,200),pygame.RESIZABLE )
        self.clock = pygame.time.Clock()
        self.hwnd = pygame.display.get_wm_info()['window']

        # Commenting the two following lines "fix" the issue
        # Meaning when not overwritting the window procedure it works well
        if platform.system() == 'Windows':
            windows_resize_procedure(self.hwnd,self.draw,self.resize,self.screen)       
        
        self.elements = []
    def inputs(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()

        # Simulating high volume of instances
        [Element() for _ in range(1000)]
    
    def resize(self,size):
        print(f'resizing to {size}')
    
    def draw(self):
        self.screen.fill('white')

    def run(self):
        while True:
            self.inputs()
            pygame.display.update()
            self.clock.tick()

if __name__ == "__main__":
    app = App()
    app.run()
python winapi ctypes
1个回答
0
投票

您似乎没有保留对

new_window_proc_cb
的引用。如果没有引用,Python 可能会垃圾收集回调对象,同时它仍然绑定到 Windows API。当回收的内存被重新用于某些不相关的目的时,结果将是稍后崩溃。

要修复它,您可能只想创建一个全局变量来存储所有已注册的 WNDPROC。

这记录在 ctypes 文档中:

注意:只要在 C 代码中使用 CFUNCTYPE() 对象,请确保保留它们的引用。 ctypes 不会,如果您不这样做,它们可能会被垃圾收集,从而在进行回调时使您的程序崩溃。

© www.soinside.com 2019 - 2024. All rights reserved.