我正在制作一个使用 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()
您似乎没有保留对
new_window_proc_cb
的引用。如果没有引用,Python 可能会垃圾收集回调对象,同时它仍然绑定到 Windows API。当回收的内存被重新用于某些不相关的目的时,结果将是稍后崩溃。
要修复它,您可能只想创建一个全局变量来存储所有已注册的 WNDPROC。
这记录在 ctypes 文档中:
注意:只要在 C 代码中使用 CFUNCTYPE() 对象,请确保保留它们的引用。 ctypes 不会,如果您不这样做,它们可能会被垃圾收集,从而在进行回调时使您的程序崩溃。