我正在制作一个应用程序,使用SDL/PYGAME显示图形.我已经重写了窗口过程,在调整大小以触发函数的情况下,使应用程序运行得更流畅(参见this answer).

然而,我注意到,当达到某个类的一定数量的实例时,它会在日志(log)中没有任何错误地使应用程序崩溃.我判断了Windows日志(log),但什么也找不到.我发现罪魁祸首是窗口程序覆盖.

下面是对该问题的模拟:

  • 函数windows_resize_procedure(受this answer启发)在用户调整窗口大小时触发App.resizeApp.draw
  • [Element() for _ in range(1000)]会人为地增加Element个实例的数量.在我的实际程序中,我只有大约50个属性,但我认为它们"更大"(更多属性和方法).[Element() for _ in range(1000)]0足以让我电脑上的程序在几秒钟内崩溃,但你可能想根据你的情况增加这个数字.
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()

推荐答案

你似乎没有保留对new_window_proc_cb的引用.如果没有引用,则当回调对象仍绑定到Windows API时,Python可能会对其进行垃圾回收.当回收的内存被重新用于一些不相关的目的时,结果将是一次崩溃.

要解决这个问题,您可能只需要创建一个全局变量来存储所有已注册的WNDPROC.

这一点记录在ctypes documentation:

注意:只要是在C代码中使用,请确保保留对CFuncyPE()对象的引用.Ctype不需要,如果不这样做,它们可能会被垃圾回收,在进行回调时会使程序崩溃.

Python相关问答推荐

Ibis中是否有一个ANY或ANY_UTE表达,可以让我比较子查询返回的一组值中的值?

如何在超时的情况下同步运行Matplolib服务器端?该过程随机挂起

无法使用python.h文件; Python嵌入错误

遵循轮廓中对象方向的计算线

LAB中的增强数组

提取两行之间的标题的常规表达

Python 3.12中的通用[T]类方法隐式类型检索

pyscript中的压痕问题

如何在UserSerializer中添加显式字段?

创建可序列化数据模型的最佳方法

删除marplotlib条形图上的底边

Python列表不会在条件while循环中正确随机化'

如何指定列数据类型

什么是合并两个embrame的最佳方法,其中一个有日期范围,另一个有日期没有任何共享列?

在pandas数据框中计算相对体积比指标,并添加指标值作为新列

在方法中设置属性值时,如何处理语句不可达[Unreacable]";的问题?

如何将数据帧中的timedelta转换为datetime

计算空值

Pandas—MultiIndex Resample—我不想丢失其他索引的信息´

在numpy数组中寻找楼梯状 struct