MASM x64调用CreateWindowExA返回NULL(错误码87)求助
问题分析与解决方案
错误码87(ERROR_INVALID_PARAMETER)的核心来源有两个,同时存在一些次要代码问题:
1. CreateWindowExA栈参数未按x64调用约定传递
x64 Windows调用约定中,所有参数(即使是32位int类型)都必须以8字节(64位)形式传递。你原代码中对X、Y、nWidth、nHeight使用dword ptr赋值,仅初始化低4字节,高4字节保留栈上的垃圾值,导致这些参数变成无效的超大数值,触发系统参数校验失败。
2. WindowProc调用DefWindowProcA时未传递必要参数
窗口创建过程中会发送WM_NCCREATE、WM_CREATE等消息,你的WindowProc直接调用DefWindowProcA但未传递hWnd、uMsg、wParam、lParam这四个必要参数,导致消息处理返回错误结果,系统终止窗口创建流程。
次要代码错误
- WNDCLASSA结构体中把
cbWndExtra字段错误注释为cbClsExtra,虽值为0不影响功能,但属于代码逻辑混淆。 - WM_CLOSE处理中未调用
PostQuitMessage,会导致窗口关闭后程序无法正常退出。
修正后的代码
ExitProcess proto MessageBoxA proto RegisterClassA proto GetModuleHandleA proto LoadIconA proto LoadCursorA proto GetStockObject proto CreateWindowExA proto DefWindowProcA proto DestroyWindow proto PostQuitMessage proto GetLastError proto .const WM_CLOSE equ 10h CS_HREDRAW equ 2h CS_VREDRAW equ 1h CS_OWNDC equ 20h BLACK_BRUSH equ 4 IDI_APPLICATION equ 32512 IDC_ARROW equ 32512 WS_OVERLAPPEDWINDOW equ 0CF0000h w32_class_name byte "snake_asm_window_class", 0 w32_window_name byte "snake", 0 w32_window_width dword 720 w32_window_height dword 1280 .code WinMainCRTStartup proc ; 栈空间计算:WNDCLASS(72) + 局部变量(32) + CreateWindowExA栈参数(96) + 影子空间(32) = 232字节(16字节对齐) sub rsp, 232 ; 获取黑色画刷 mov ecx, BLACK_BRUSH call GetStockObject mov qword ptr [rsp + 160], rax ; 加载应用程序图标 xor rcx, rcx mov edx, IDI_APPLICATION call LoadIconA mov qword ptr [rsp + 152], rax ; 加载箭头光标 xor rcx, rcx mov edx, IDC_ARROW call LoadCursorA mov qword ptr [rsp + 144], rax ; 获取当前模块句柄 xor rcx, rcx call GetModuleHandleA mov qword ptr [rsp + 136], rax ; 构造WNDCLASSA结构体 lea rcx, [rsp + 168] ; WNDCLASS首地址 mov dword ptr [rcx + 0], CS_HREDRAW or CS_VREDRAW or CS_OWNDC ; 窗口类风格 lea rax, [WindowProc] mov qword ptr [rcx + 8], rax ; 窗口过程指针 mov dword ptr [rcx + 16], 0 ; cbClsExtra mov dword ptr [rcx + 20], 0 ; cbWndExtra mov rax, qword ptr [rsp + 136] mov qword ptr [rcx + 24], rax ; hInstance mov rax, qword ptr [rsp + 152] mov qword ptr [rcx + 32], rax ; hIcon mov rax, qword ptr [rsp + 144] mov qword ptr [rcx + 40], rax ; hCursor mov rax, qword ptr [rsp + 160] mov qword ptr [rcx + 48], rax ; hbrBackground mov qword ptr [rcx + 56], 0 ; lpszMenuName lea rax, w32_class_name mov qword ptr [rcx + 64], rax ; lpszClassName ; 注册窗口类 call RegisterClassA test rax, rax jz exit_proc ; 可选:处理注册失败的情况 ; 调用CreateWindowExA xor rcx, rcx ; dwExStyle = 0 lea rdx, w32_class_name ; lpClassName lea r8, w32_window_name ; lpWindowName mov r9d, WS_OVERLAPPEDWINDOW ; dwStyle ; 栈上参数(按X、Y、nWidth、nHeight、hWndParent、hMenu、hInstance、lpParam顺序) mov qword ptr [rsp + 32], 0 ; X = CW_USEDEFAULT(0) mov qword ptr [rsp + 40], 0 ; Y = CW_USEDEFAULT(0) mov eax, w32_window_width mov qword ptr [rsp + 48], rax ; nWidth mov eax, w32_window_height mov qword ptr [rsp + 56], rax ; nHeight mov qword ptr [rsp + 64], 0 ; hWndParent = NULL mov qword ptr [rsp + 72], 0 ; hMenu = NULL mov rax, qword ptr [rsp + 136] mov qword ptr [rsp + 80], rax ; hInstance mov qword ptr [rsp + 88], 0 ; lpParam = NULL call CreateWindowExA test rax, rax jz get_error ; 处理窗口创建失败 exit_proc: xor rcx, rcx call ExitProcess get_error: call GetLastError ; 可选:输出错误码 jmp exit_proc WinMainCRTStartup endp ; 标准x64窗口过程参数:hWnd, uMsg, wParam, lParam WindowProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM ; 栈对齐:进入函数时rsp偏移8字节(返回地址),sub rsp 40后,rsp+40+8=48(16的倍数),满足对齐要求 sub rsp, 40 cmp uMsg, WM_CLOSE je handle_wm_close ; 将参数传递给DefWindowProcA mov rcx, hWnd mov rdx, uMsg mov r8, wParam mov r9, lParam call DefWindowProcA jmp done handle_wm_close: ; 销毁窗口 mov rcx, hWnd call DestroyWindow ; 发送退出消息 mov rcx, 0 call PostQuitMessage xor rax, rax ; 返回0表示消息已处理 done: add rsp, 40 ret WindowProc endp end
关键修正点说明
- 栈参数传递:所有CreateWindowExA的栈参数都使用
qword ptr赋值,确保64位参数的完整性。 - WindowProc参数传递:明确声明窗口过程的参数,并将这些参数正确传递给DefWindowProcA,保证消息处理流程正常。
- 代码可读性优化:增加了常量定义,替换魔法数值,修正结构体注释,添加错误分支处理。
- 栈空间对齐:调整栈空间大小为16字节对齐的数值,符合x64调用约定要求。
内容的提问来源于stack exchange,提问作者cjxo_0
相关产品推荐
相关产品推荐

