窗口过程重写导致创建大量类实例时程序无报错崩溃
看起来你碰到了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

