WM_DESTROY x64 中的 WndProc 未收到消息 Windows 汇编程序

WM_DESTROY message not being received by WndProc in x64 Windows Assembly Program

我正在尝试使用 MASM /w Visual Studio 2019 重新创建类似于以下有效的 C++ 代码的内容。基本上在这个阶段只希望 window 可以移动并且关闭按钮可以工作。

#include <iostream>
#include <Windows.h>

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM
    lParam)
{
    switch (message)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

int main()
{
    wchar_t windowclass[] = L"MyWinTest";

    HINSTANCE hInstance = GetModuleHandleW(NULL);
    MSG msg;
    WNDCLASSEXW wc;
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hbrBackground = 0;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
    wc.hIconSm = NULL;
    wc.hInstance = hInstance;
    wc.lpfnWndProc = WndProc;
    wc.lpszClassName = windowclass;
    wc.lpszMenuName = nullptr;
    wc.style = CS_HREDRAW | CS_VREDRAW;
    
    RegisterClassExW(&wc);

    HWND hWnd = CreateWindowExW(
        0, 
        windowclass,
        L"MyWindow",
        WS_OVERLAPPEDWINDOW, 
        CW_USEDEFAULT, 
        CW_USEDEFAULT,
        800, 
        600, 
        NULL, 
        NULL, 
        hInstance, 
        NULL);

        ShowWindow(hWnd, SW_SHOW);
        UpdateWindow(hWnd);

        while (GetMessage(&msg, NULL, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

}

在我使用 MASM 的 x64 程序集版本中,创建并显示了 Window,同时 WndProc 也被命中,可以看到它得到消息 WM_CREATE 和 WM_PAINT 但是 Window不能移动也不能关闭。我确实将 C++ 版本编译为 x64 并输出汇编列表以进行比较,但据我所知,它看起来与汇编版本非常相似。

WSTR MACRO lbl:req,qstr:VARARG
LOCAL arg,unq,qot,q
    lbl LABEL WORD
    FOR arg,<qstr>
        qot SubStr <arg>,1,1
        q = 0
        IFIDNI qot,<!'>;'
            q = 1
        ELSEIFIDNI qot,<!">;"
            q = 1
        ELSE
            DW arg
        ENDIF
        IF q EQ 1
            unq SubStr <arg>,2,@SizeStr(<arg>)-2
        %   FORC c,<unq>
                DW "&c"
            ENDM
        ENDIF
    ENDM
    DW 0
ENDM

L MACRO qstr:VARARG
LOCAL sym,seg
    seg EQU <.code>
    %IFIDNI <@CurSeg>,<_DATA>
        seg EQU <.data>
    ENDIF
    .CONST
        ALIGN 4
        WSTR sym,qstr
    seg
    EXITM <OFFSET sym>
ENDM

extrn   LoadCursorW: PROC
extrn   MessageBoxW: PROC
extrn   ExitProcess: PROC
extrn   GetModuleHandleW: PROC
extrn   RegisterClassExW: PROC
extrn   CreateWindowExW: PROC
extrn   GetLastError: PROC
extrn   DefWindowProcW: PROC
extrn   ShowWindow: PROC
extrn   Sleep: PROC
extrn   GetMessageW: PROC
extrn   TranslateMessage: PROC
extrn   DispatchMessageW: PROC
extrn   DestroyWindow: PROC
extrn   UpdateWindow: PROC
extrn   PostQuitMessage: PROC
extrn   BeginPaint: PROC
extrn   EndPaint: PROC
.data
wstr windowClassName,"AsmTestClass",0,0
wstr windowTitle,"AsmTest",0,0

HWND_DESKTOP        textequ <0h>
MB_OK           textequ <0h>

INFINITE            textequ <0ffffffffh>
WM_CREATE       textequ <0001h>
WM_DESTROY      textequ <0002h>
WM_SIZE         textequ <0005h>
WM_PAINT        textequ <000fh>
WM_CLOSE            textequ <0010h>
WM_QUIT         textequ <0012h>
SW_HIDE         textequ <0000h>
SW_SHOW         textequ <0005h>

CS_VREDRAW      textequ <0001h>
CS_HREDRAW      textequ <0002h>

WS_OVERLAPPED   textequ <00000000h>
WS_CAPTION      textequ <00c00000h>
WS_SYSMENU      textequ <00080000h>
WS_MINIMIZEBOX  textequ <00020000h>

CW_USEDEFAULT   textequ <80000000h>

IDI_APPLICATION textequ <00007f00h>

WINDOW_WIDTH        DWORD   800
WINDOW_HEIGHT   DWORD   600

WNDCLASSEX STRUCT DWORD
    cbSize          DWORD   ?
    style           DWORD   ?
    lpfnWndProc     QWORD   ?
    cbClsExtra      DWORD   ?
    cbWndExtra      DWORD   ?
    hInstance       QWORD   ?
    hIcon           QWORD   ?
    hCursor         QWORD   ?
    hbrBackground   QWORD   ?
    lpszMenuName    QWORD   ?
    lpszClassName   QWORD   ?
    hIconSm         QWORD   ?
WNDCLASSEX ENDS

MSG STRUCT 
    hwnd                QWORD   ?
    message         DWORD   ?
    wParam          QWORD   ?
    lParam          QWORD   ?
    time                DWORD   ?
    x               DWORD   ?
    y               DWORD   ?
MSG ENDS

PAINTSTRUCT STRUCT 8
    hdc             QWORD   ?
    fErase          DWORD   ?
    left                DWORD   ?
    top             DWORD   ?
    right           DWORD   ?
    bottom          DWORD   ?
    fRestore            DWORD   ?
    fIncUpdate      DWORD   ?
    rgbReserved     BYTE        32 DUP (?)
PAINTSTRUCT ENDS

.code
main proc

LOCAL wc:WNDCLASSEX
LOCAL hWnd:QWORD
LOCAL hInstance:QWORD
LOCAL hCursor:QWORD
LOCAL ATOM:WORD
LOCAL message:MSG
    
    sub     rsp, 8

    ; hInstance = GetModuleHandle(NULL)
    mov     rcx, 0
    call        GetModuleHandleW
    mov     hInstance, rax

    ; hCursor = LoadCursor(NULL,IDI_APPLICATION)
    mov     edx, IDI_APPLICATION
    xor     ecx, ecx
    call        LoadCursorW
    mov     hCursor, rax
  
    ; Setup Window Class
    mov     wc.cbSize, SIZEOF WNDCLASSEX
    mov     wc.style, CS_VREDRAW or CS_HREDRAW
    lea     rax, OFFSET WndProc
    mov     wc.lpfnWndProc, rax
    mov     wc.cbClsExtra, 0
    mov     wc.cbWndExtra, 0
    lea     rax, hInstance
    mov     wc.hInstance, rax
    mov     wc.hbrBackground, 0
    mov     wc.lpszMenuName, 0
    lea     rax, hCursor
    mov     wc.hCursor, rax
    lea     rax, windowClassName
    mov     wc.lpszClassName, rax
    mov     wc.hIconSm, 0
    lea     rcx, wc
    call        RegisterClassExW
    mov     ATOM, ax

    ; CreateWindowExW
    mov     QWORD PTR [rsp+88], 0               ;   lpParam
    lea     rax, hInstance
    mov     QWORD PTR [rsp+80], rax             ;   hInstance
    mov     QWORD PTR [rsp+72], 0               ;   hMenu
    mov     QWORD PTR [rsp+64], 0               ;   hWndParent
    mov     edx, WINDOW_HEIGHT
    mov     DWORD PTR [rsp+56], edx             ;   nHeight
    mov     edx, WINDOW_WIDTH
    mov     DWORD PTR [rsp+48], edx             ;   nWidth
    mov     DWORD PTR [rsp+40], CW_USEDEFAULT   ;   Y
    mov     DWORD PTR [rsp+32], CW_USEDEFAULT   ;   X
    mov     r9d, WS_OVERLAPPED or WS_CAPTION or WS_SYSMENU or WS_MINIMIZEBOX        ; dwStyle
    lea     r8, windowTitle                     ;   lpWindowName
    lea     rdx, windowClassName                    ;   lpClassName
    xor     ecx,ecx                             ;   dwExStyle
    call        CreateWindowExW

    cmp     rax,  0
    je      WindowFailed
    jmp     WindowSuccess

WindowFailed:
    call        GetLastError
    ; to-do check error ?

WindowSuccess:
    mov     hWnd, rax

    mov     rcx, hWnd       ; hWnd
    mov     edx, SW_SHOW        ; nCmdShow
    call        ShowWindow

    mov     rcx, hWnd       ; hWnd
    call        UpdateWindow

MessageLoop:
    xor     r9d, r9d            ; wMsgFilterMax
    xor     r8d, r8d            ; wMsgFilterMin
    xor     edx, edx            ; hWnd
    lea     rcx, message        ; lpMsg
    call        GetMessageW

    test        eax, eax
    je      QuitMessageLoop

    lea     rcx, message            ; lpMsg
    call    TranslateMessage

    lea     rcx, message            ; lpMsg
    call        DispatchMessageW
       
    jmp     MessageLoop 

QuitMessageLoop: 
    mov     ecx, eax                            ; uExitCode
    call        ExitProcess

main endp

WndProc proc
    LOCAL hWnd:QWORD
    LOCAL uMsg:DWORD
    LOCAL wParam:QWORD
    LOCAL lParam:QWORD
    LOCAL result:QWORD
    LOCAL ps:PAINTSTRUCT
    LOCAL hdc:QWORD

    sub     rsp, 8
    mov     lParam, r9
    mov     wParam, r8
    mov     uMsg, edx
    mov     hWnd, rcx
    
    ; msg handler
    cmp     uMsg,WM_CREATE
    je      create
    cmp     uMsg,WM_PAINT
    je      paint   
    cmp     uMsg,WM_DESTROY
    je      destroy

    ; default
    mov     r9, lParam
    mov     r8, wParam
    mov     edx, uMsg
    mov     rcx, hWnd
    call        DefWindowProcW
    mov     result,rax
    jmp     finish

create:
    mov     result, 0
    jmp     finish

paint:
    mov     rcx, hWnd
    lea     rdx, ps
    call        BeginPaint
    mov     hdc, rax

    ; to-do HDC paint stuff here

    mov     rcx, hWnd
    lea     rdx, ps
    call        EndPaint

    mov     result, 0
    jmp     finish

destroy:
    xor     ecx, ecx            ; nExitCode
    call        PostQuitMessage
    mov     result, 0
    jmp     finish

finish: 
    mov     rax, result
    
    ret 

WndProc endp
End

注意由于本地语句 masm 自动添加到我的 main 之前的 sub rsp,8: (-152 + -8 = -160 )

push    rbp
mov     rbp, rsp
add     rsp, 0FFFFFFFFFFFFFF68h

并添加到 wndproc 例程 (-120 + -8 = -128)

    push    rbp
mov     rbp, rsp
add     rsp, 0FFFFFFFFFFFFFF88h

...

leave

代码错误 - 在两个过程中 -

sub rsp, 8

x64 calling convention

The caller is responsible for allocating space for the callee's parameters. The caller must always allocate sufficient space to store four register parameters, even if the callee doesn't take that many parameters.

所以需要 - 存储四个寄存器参数 - 4*8 和 +8 用于 16 字节对齐,所以

sub rsp, 40

在具体情况下,对 DispatchMessageWTranslateMessage 的调用可能会损坏(覆盖)message,因为它位于被调用方的参数 space 中。如果在调试器下测试 - 我认为 DispatchMessageW 在 prolog 中覆盖了 message 而不是已经使用了错误的 message