You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

窗口过程重写导致创建大量类实例时程序无报错崩溃

窗口过程重写导致创建大量类实例时程序无报错崩溃

看起来你碰到了ctypes和Windows API交互时的经典陷阱——回调函数的Python对象被垃圾回收,直接引发了底层内存访问错误,这种崩溃通常不会留下日志,因为是系统级的内存异常。

问题根源分析

在你的windows_resize_procedure函数里,new_window_proc_cb是用WNDPROC包装的回调对象,但它只是函数内部的局部变量。当函数执行完毕后,Python的垃圾回收器会认为这个变量不再被引用,随时可能回收它占用的内存。但Windows系统还持有指向这个回调的指针,当窗口收到消息(比如resize事件)时,会尝试调用已经被回收的内存地址,直接导致程序崩溃。

为什么创建大量Element实例会触发这个问题?因为大量对象会加速Python的垃圾回收频率——平时GC可能没那么快启动,但实例多了之后内存占用上升,GC就会运行,刚好把回调对象回收掉,触发崩溃。

另外你的代码还有个小问题:原代码里用user32.GetForegroundWindow()获取窗口句柄,这是错误的,应该直接用传入的Pygame窗口句柄hwnd,否则如果你的程序不是前台窗口,会错误地修改其他窗口的过程,引发不可预料的问题。

解决方案:保存回调对象的引用

核心思路是让回调对象在窗口生命周期内一直被引用,避免被垃圾回收。我们可以把回调对象保存到App实例里,确保只要App存在,回调就不会被回收。

修改后的代码示例

首先调整windows_resize_procedure函数,让它返回回调对象:

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

        user32 = ctypes.windll.user32

        # 修正WPARAM/LPARAM的类型,不需要用POINTER,它们本身是整数类型
        WNDPROC = ctypes.WINFUNCTYPE(
            ctypes.c_long,
            wintypes.HWND,
            ctypes.c_uint,
            wintypes.WPARAM,
            wintypes.LPARAM
        )

        WM_SIZE = 0x0005
        RDW_INVALIDATE = 0x0001
        RDW_ERASE = 0x0004
        GWL_WNDPROC = -4

        # 用传入的hwnd(Pygame窗口句柄)代替GetForegroundWindow()
        old_window_proc = user32.GetWindowLongPtrA(
            hwnd,
            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)
        # 修正类型转换,直接用c_void_p更准确
        user32.SetWindowLongPtrA(
            hwnd,
            GWL_WNDPROC,
            ctypes.cast(new_window_proc_cb, ctypes.c_void_p).value
        )
        # 返回回调对象,让调用者保存引用
        return new_window_proc_cb
    except Exception as e:
        print(e)
        return None

然后修改App类,保存回调对象的引用:

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']
        # 新增属性保存回调引用,避免被GC回收
        self.window_proc_cb = None
        if platform.system() == 'Windows':
            self.window_proc_cb = windows_resize_procedure(self.hwnd,self.draw,self.resize,self.screen)
        self.elements = []

    # 其他方法保持不变...

为什么这样能解决问题?

通过把new_window_proc_cb保存到App实例的window_proc_cb属性中,只要App实例存在(也就是程序运行期间),这个回调对象就会一直被Python引用,垃圾回收器不会回收它的内存。Windows系统调用回调时,访问的是有效的内存地址,自然就不会崩溃了。

备注:内容来源于stack exchange,提问作者Aradyan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.21 12:59:36