汇编实现Pong游戏后无法创建窗口的问题求助
问题描述
为深入理解计算机底层运行机制,用汇编语言编写了Pong游戏。此前代码可正常运行,未修改任何代码,关闭误报为恶意程序的杀毒软件并重启电脑后,现在无法创建窗口。编译时仅出现警告hello.asm(51) : warning A6001: no return from procedure,此前该警告不影响运行,目前无其他异常报错,寻求解决方法。
原代码:
.386 .model flat, stdcall option casemap:none include C:\MASM32\include\masm32rt.inc includelib user32.lib includelib kernel32.lib .data txClass db "MyWinClass", 0 wcx WNDCLASSEX <WNDCLASSEX, CS_HREDRAW or CS_VREDRAW, WndProc, 0, 0, 1, 2, 3, COLOR_BTNFACE+1, 0, txClass, 4> rect RECT <20, 0, 40, 100> ; Rectangle to hold the client area dimensions ball RECT <200, 200, 210,210> ; Rectangle to hold the client area dimensions ballMX DWORD 5 ballMY DWORD 5 .data? ; uninitialised data - use for handles etc hMenu dd? ps PAINTSTRUCT<?> dwStartTime DWORD? dwElapsedTime DWORD? hWnd2 DWORD? .code WinMain proc uses ebx LOCAL msg:MSG mov ebx, offset wcx wc equ [ebx.WNDCLASSEX] mov wc.hInstance, rv(GetModuleHandle, 0) mov wc.hIcon, rv(LoadIcon, NULL, IDI_APPLICATION) mov wc.hIconSm, eax invoke RegisterClassEx, addr wc invoke CreateWindowEx, NULL, wc.lpszClassName, chr$("Pong"), WS_OVERLAPPEDWINDOW or WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, wc.hInstance, NULL mov hWnd2, eax invoke SetTimer, hWnd2, 1, 8, NULL .While 1 invoke GetMessage, ADDR msg, NULL, 0, 0 .Break.if!eax invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .Endw exit msg.wParam WinMain endp WndProc proc uses esi edi ebx hWnd, uMsg, wParam:WPARAM, lParam:LPARAM LOCAL hdc: HDC SWITCH uMsg CASE WM_CREATE ret CASE WM_KEYDOWN mov eax, wParam SWITCH eax CASE VK_S add dword ptr [rect.top], 5 add dword ptr [rect.bottom], 5 CASE VK_W sub dword ptr [rect.top], 5 sub dword ptr [rect.bottom], 5 ret ret ENDSW invoke InvalidateRect, hWnd, NULL, TRUE ; Force the window to repaint ret CASE WM_TIMER push ebx mov eax, ball.bottom mov ebx, ps.rcPaint.bottom cmp eax,ebx jb cont neg ballMY cont: mov eax,ballMY add ball.top, eax add ball.bottom, eax xor ebx, ebx mov eax, ball.right mov ebx, ps.rcPaint.right cmp eax,ebx jb cont2 neg ballMX cont2: mov eax,ballMX add ball.left, eax add ball.right, eax pop ebx invoke InvalidateRect, hWnd, NULL, TRUE ; Request a repaint CASE WM_PAINT invoke BeginPaint, hWnd, ADDR ps ; Start painting the window mov hdc, eax ; Store the device context handle invoke CreateSolidBrush, 0000000 invoke FillRect, hdc, ADDR ps.rcPaint, eax invoke CreateSolidBrush, 0FFFFFFh invoke FillRect, hdc, ADDR rect, eax invoke CreateSolidBrush, 0FFFFFFh invoke FillRect, hdc, ADDR ball, eax invoke EndPaint, hWnd, ADDR ps ; End painting ret CASE WM_DESTROY invoke PostQuitMessage, NULL ENDSW invoke DefWindowProc, hWnd, uMsg, wParam, lParam ret WndProc endp end WinMain
解决方法
1. 修复窗口过程(WndProc)的流程错误
原代码中多个消息分支存在ret位置错误或缺失,导致窗口过程逻辑混乱,这是窗口无法创建的核心原因:
- WM_KEYDOWN分支:VK_W处理后直接ret,VK_S处理后没有ret,且ENDSW位置错误,导致InvalidateRect永远不会执行。调整为所有按键处理完后统一调用InvalidateRect再ret。
- WM_TIMER分支:处理完后没有ret,会继续执行后续的WM_PAINT代码,引发严重错误,必须添加ret。
- WM_DESTROY分支:处理完PostQuitMessage后添加ret,避免执行多余的DefWindowProc调用。
2. 修复全局PAINTSTRUCT的错误使用
全局变量ps仅在WM_PAINT中被BeginPaint初始化,WM_TIMER中直接访问ps.rcPaint会读取到未初始化的垃圾值,导致球的碰撞逻辑错误。改用GetClientRect获取窗口客户区大小进行碰撞检测。
3. 修复窗口类的光标初始化错误
原WNDCLASSEX初始化时将hCursor设为3(无效句柄),改为加载系统默认箭头光标,避免窗口类注册异常。
4. 添加错误检查
在RegisterClassEx和CreateWindowEx后添加返回值检查,方便定位窗口创建失败的具体原因。
修改后的代码
.386 .model flat, stdcall option casemap:none include C:\MASM32\include\masm32rt.inc includelib user32.lib includelib kernel32.lib .data txClass db "MyWinClass", 0 ; 修复hCursor初始化,使用系统默认箭头光标 wcx WNDCLASSEX <WNDCLASSEX, CS_HREDRAW or CS_VREDRAW, WndProc, 0, 0, 0, 0, rv(LoadCursor, NULL, IDC_ARROW), COLOR_BTNFACE+1, 0, txClass, 0> rect RECT <20, 0, 40, 100> ; 玩家 paddle ball RECT <200, 200, 210,210> ; 球 ballMX DWORD 5 ballMY DWORD 5 clientRect RECT <?> ; 新增存储客户区大小的变量 .data? hMenu dd? ps PAINTSTRUCT<?> dwStartTime DWORD? dwElapsedTime DWORD? hWnd2 DWORD? .code WinMain proc uses ebx LOCAL msg:MSG LOCAL hInst:HINSTANCE mov hInst, rv(GetModuleHandle, 0) mov ebx, offset wcx wc equ [ebx.WNDCLASSEX] mov wc.hInstance, hInst mov wc.hIcon, rv(LoadIcon, NULL, IDI_APPLICATION) mov wc.hIconSm, eax ; 检查窗口类注册是否成功 invoke RegisterClassEx, addr wc .if eax == 0 invoke MessageBox, NULL, chr$("窗口类注册失败"), chr$("错误"), MB_OK exit 1 .endif ; 创建窗口并检查是否成功 invoke CreateWindowEx, NULL, wc.lpszClassName, chr$("Pong"), WS_OVERLAPPEDWINDOW or WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInst, NULL mov hWnd2, eax .if eax == 0 invoke MessageBox, NULL, chr$("窗口创建失败"), chr$("错误"), MB_OK exit 1 .endif invoke SetTimer, hWnd2, 1, 8, NULL .While 1 invoke GetMessage, ADDR msg, NULL, 0, 0 .Break.if!eax invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .Endw exit msg.wParam WinMain endp WndProc proc uses esi edi ebx hWnd, uMsg, wParam:WPARAM, lParam:LPARAM LOCAL hdc: HDC SWITCH uMsg CASE WM_CREATE ret CASE WM_KEYDOWN mov eax, wParam SWITCH eax CASE VK_S add dword ptr [rect.top], 5 add dword ptr [rect.bottom], 5 CASE VK_W sub dword ptr [rect.top], 5 sub dword ptr [rect.bottom], 5 ENDSW ; 修正ENDSW位置 invoke InvalidateRect, hWnd, NULL, TRUE ret CASE WM_TIMER ; 获取当前窗口客户区大小 invoke GetClientRect, hWnd, ADDR clientRect push ebx ; 检查球是否碰到上下边界 mov eax, ball.bottom mov ebx, clientRect.bottom cmp eax, ebx jb contY neg ballMY contY: mov eax, ball.top cmp eax, 0 jge contY2 neg ballMY contY2: mov eax, ballMY add ball.top, eax add ball.bottom, eax ; 检查球是否碰到左右边界 mov eax, ball.right mov ebx, clientRect.right cmp eax, ebx jb contX neg ballMX contX: mov eax, ball.left cmp eax, 0 jge contX2 neg ballMX contX2: mov eax, ballMX add ball.left, eax add ball.right, eax pop ebx invoke InvalidateRect, hWnd, NULL, TRUE ret ; 添加ret,避免执行后续代码 CASE WM_PAINT invoke BeginPaint, hWnd, ADDR ps mov hdc, eax ; 绘制背景 invoke CreateSolidBrush, 000000h invoke FillRect, hdc, ADDR ps.rcPaint, eax invoke DeleteObject, eax ; 释放GDI资源,避免内存泄漏 ; 绘制玩家 paddle invoke CreateSolidBrush, 0FFFFFFh invoke FillRect, hdc, ADDR rect, eax invoke DeleteObject, eax ; 绘制球 invoke CreateSolidBrush, 0FFFFFFh invoke FillRect, hdc, ADDR ball, eax invoke DeleteObject, eax invoke EndPaint, hWnd, ADDR ps ret CASE WM_DESTROY invoke KillTimer, hWnd, 1 ; 销毁定时器 invoke PostQuitMessage, NULL ret ; 添加ret ENDSW invoke DefWindowProc, hWnd, uMsg, wParam, lParam ret WndProc endp end WinMain
额外说明
- 新增了
DeleteObject调用释放GDI画笔资源,避免内存泄漏。 - 完善了球的边界碰撞检测,增加了顶部和左边界的判断。
- 添加了定时器销毁逻辑,程序退出时清理资源。
内容的提问来源于stack exchange,提问作者Jannis
相关产品推荐
相关产品推荐

