fasm x64 windows gdi 编程困难 - 调用 stretchdibits 未按预期绘制屏幕

fasm x64 windows gdi programming struggles - call to stretchdibits not painting screen as expected

我有一个简单的 fasm 程序,在这个程序中,我通过 VirtualAlloc 从 windows 获得了一些归零内存。然后我有一个过程,我只需设置参数并调用 StretchDIBits 传递一个指向空内存缓冲区的指针。因此,我希望屏幕应该画成黑色。然而事实并非如此,我这辈子也想不通为什么。

下面是代码。

format PE64 GUI 
entry main 

include 'C:\Users\bmowo\Desktop\Tools\fasm\INCLUDE\win64a.inc'
include '.\main_data.asm'

section '.text' code readable executable 
main:
sub rsp,8 ;alignment

invoke  GetModuleHandle,0
mov [WindowClass.hInstance], rax
invoke LoadIcon,0,IDI_APPLICATION
mov [WindowClass.hIcon],rax
mov [WindowClass.hIconSm],rax
invoke LoadCursor,0,IDC_ARROW
mov [WindowClass.hCursor],rax

mov rax, CS_OWNDC or CS_HREDRAW or CS_VREDRAW
mov [WindowClass.style], eax

invoke  RegisterClassExA,WindowClass
test rax, rax
jz exit 

invoke CreateWindowExA,0,WindowClassName,WindowTitle, WS_VISIBLE+WS_OVERLAPPEDWINDOW,\
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0,0,[WindowClass.hInstance],0

mov [WindowHandle], rax
test rax, rax
jz exit

mov rax, 2*gigabyte 
mov r10d, MEM_COMMIT or MEM_RESERVE
invoke VirtualAlloc,0,eax,r10d,PAGE_READWRITE
mov [Memory], rax

mov [GlobalRunning], 1

.main_loop:
cmp [GlobalRunning], 0
je exit

stdcall Win32MessagePump
jmp .main_loop

exit:
invoke  ExitProcess,[Message.wParam]

proc BlitBuffer
locals
    Width dd 0      
    Height dd 0
    PixelsBase dq 0
    DC dq 0
    BitmapInfo BITMAPINFO 0
    ScreenRect RECT 
endl

;get window region
lea rax, [ScreenRect]
invoke GetClientRect, [WindowHandle], rax

;calculate width and height
mov ecx, [ScreenRect.bottom]
sub ecx, [ScreenRect.top]
mov [Height], ecx

mov ecx, [ScreenRect.left]
sub ecx, [ScreenRect.right]
mov [Width], ecx


BitmapInfoHeaderSize equ 40
BI_RGB equ 0
mov [BitmapInfo.biSize], BitmapInfoHeaderSize ;sizeof bitmapinfoheader is 40
mov eax, [Width]
mov [BitmapInfo.biWidth], eax
mov eax, [Height]
mov [BitmapInfo.biHeight], eax
mov [BitmapInfo.biPlanes], 1
mov [BitmapInfo.biBitCount], 32
mov [BitmapInfo.biCompression], BI_RGB
mov [BitmapInfo.biSizeImage], 0
mov [BitmapInfo.biXPelsPerMeter], 0
mov [BitmapInfo.biYPelsPerMeter], 0
mov [BitmapInfo.biClrUsed], 0 
mov [BitmapInfo.biClrImportant], 0
mov [BitmapInfo.RGBQUADa], 0
mov [BitmapInfo.RGBQUADb], 0
mov [BitmapInfo.RGBQUADc], 0
mov [BitmapInfo.RGBQUADd], 0

mov rax, [Memory] 
mov [PixelsBase], rax

invoke GetDC, [WindowHandle]
mov [DC], rax

lea rax, [BitmapInfo]

DIB_RGB_COLORS equ 0
invoke StretchDIBits, [DC],0,0,[Width],[Height],0,0,[Width],[Height],[PixelsBase], rax,DIB_RGB_COLORS,SRCCOPY

ret
endp 

proc Win32ToggleWindowFullScreen

locals
    WindowStyle dd 0
    MonitorInfo MONITORINFO sizeof.MONITORINFO
endl

GWL_STYLE equ -16
SWP_NOOWNERZORDER equ 0x0200 
SWP_FRAMECHANGED equ 0x0020
SWP_NOMOVE equ 0x0002 
SWP_NOSIZE equ 0x0001 
SWP_NOZORDER equ 0x0004
HWND_TOP equ 0 
SWP_FRAMECHANGED equ 0x0020
WS_OVERLAPPEDWINDOW equ 0xcf0000
MONITOR_DEFAULTTOPRIMARY equ 1 

invoke GetWindowLongA, [WindowHandle], GWL_STYLE
mov [WindowStyle], eax

mov rbx, WS_OVERLAPPEDWINDOW
and eax, ebx
jz .else

invoke GetWindowPlacement, [WindowHandle], GlobalWindowPlacement
mov r8, rax
cmp rax, 1
jb .end 

invoke MonitorFromWindow,[WindowHandle], MONITOR_DEFAULTTOPRIMARY
lea rbx, [MonitorInfo]
invoke GetMonitorInfoA,rax,rbx
cmp rax, 1
jb .end

mov rbx, not WS_OVERLAPPEDWINDOW ;not rbx
mov eax, [WindowStyle]
and eax, ebx

invoke SetWindowLongA, [WindowHandle], GWL_STYLE, eax

mov eax, [MonitorInfo.rcMonitor.right]
sub eax, [MonitorInfo.rcMonitor.left]

mov r10d, [MonitorInfo.rcMonitor.bottom]
sub r10d, [MonitorInfo.rcMonitor.top] 

mov r11, HWND_TOP or SWP_NOOWNERZORDER or SWP_FRAMECHANGED 

invoke SetWindowPos, [WindowHandle],0,[MonitorInfo.rcMonitor.left],[MonitorInfo.rcMonitor.top],eax,r10d,r11d
jmp .end

.else:
mov eax, [WindowStyle]
or rax, WS_OVERLAPPEDWINDOW
invoke SetWindowLongA, [WindowHandle],GWL_STYLE,eax
invoke SetWindowPlacement,[WindowHandle],GlobalWindowPlacement

mov rax, SWP_NOOWNERZORDER or SWP_FRAMECHANGED or SWP_NOMOVE or SWP_NOSIZE or SWP_NOZORDER 
invoke SetWindowPos,[WindowHandle],0,0,0,0,0,eax

.end:
ret
endp

proc Win32MessagePump

.while_message:  

invoke PeekMessageA,Message,[WindowHandle],0,0,PM_REMOVE 
cmp eax, 0
je .end

cmp [Message.message], WM_KEYDOWN
je .keydown

cmp [Message.message], WM_PAINT
je .paint

.default:
invoke TranslateMessage,Message
invoke DispatchMessage,Message
jmp .while_message

.keydown:
stdcall Win32ToggleWindowFullScreen

.paint:

    invoke BeginPaint,[WindowHandle],PaintStruct

    stdcall BlitBuffer

    invoke EndPaint,[WindowHandle],PaintStruct

jmp .while_message

.end:

ret
endp

proc Win32CallbackProc ;hwnd,wmsg,wparam,lparam

cmp edx, WM_DESTROY
    je .close 
cmp edx, WM_CLOSE
    je .close
cmp edx, WM_QUIT
    je .close

.default:
    invoke  DefWindowProcA,rcx,rdx,r8,r9
    jmp .end

.close:
    mov [GlobalRunning], 0
    jmp .end
            
.end:
ret
endp

'''

GlobalWindowPlacement WINDOWPLACEMENT sizeof.WINDOWPLACEMENT

breakit equ int3

kilobyte equ 1024
megabyte equ 1024*1024
gigabyte equ 1024*1024*1024

struct BITMAPINFO
biSize                  dd ?
biWidth                 dd ?
biHeight                dd ?
biPlanes                dw ?
biBitCount          dw ?
biSizeImage         dd 0
biXPelsPerMeter dd 0
biYPelsPerMeter dd 0
biClrUsed               dd 0
biClrImportant  dd 0
RGBQUADa                db 0
RGBQUADb                db 0 
RGBQUADc                db 0
RGBQUADd                db 0
ends

PaintStruct PAINTSTRUCT 

struct MONITORINFO 
cbSize          dd ?
rcMonitor   RECT ?
rcWork      RECT ?
dwFlags         dd ?
ends

section '.data' data readable writeable 

GlobalRunning db 0

WindowClassName db "fasm app",0
WindowTitle db "Raytracer or Rasteriser or somethin",0

;WindowClass WNDCLASSEX sizeof.WNDCLASSEX,0,Win32CallbackProc,0,0,0,0,0,COLOR_WINDOW,0,WindowClassName,0
WindowClass WNDCLASSEX sizeof.WNDCLASSEX,0,Win32CallbackProc,0,0,0,0,0,0,0,WindowClassName,0
Message MSG
WindowHandle dq 0
WindowDC dq 0
Memory dq 0

section '.idata' import data readable writeable

library kernel,'kernel32.dll',\
  user,'user32.dll',\
    gdi, 'gdi32.dll'

import kernel,\
 GetModuleHandle,'GetModuleHandleA',\
 ExitProcess,'ExitProcess',\
 VirtualAlloc, 'VirtualAlloc',\
 VirtualFree, 'VirtualFree',\
 GetLastError, 'GetLastError',\
 SetLastError, 'SetLastError'\

import user,\
 RegisterClassExA,'RegisterClassExA',\
 CreateWindowExA,'CreateWindowExA',\
 ShowWindow,'ShowWindow',\
 UpdateWindow,'UpdateWindow',\
 DefWindowProcA,'DefWindowProcA',\
 GetMessage,'GetMessageA',\
 TranslateMessage,'TranslateMessage',\
 DispatchMessage,'DispatchMessageA',\
 LoadCursor,'LoadCursorA',\
 LoadIcon,'LoadIconA',\
 GetClientRect,'GetClientRect',\
 GetDC,'GetDC',\
 ReleaseDC,'ReleaseDC',\
 BeginPaint,'BeginPaint',\
 EndPaint,'EndPaint',\
 PostQuitMessage,'PostQuitMessage',\
 MessageBoxA, 'MessageBoxA',\
 PeekMessageA, 'PeekMessageA',\
 GetWindowLongA, 'GetWindowLongA',\
 GetWindowPlacement,'GetWindowPlacement',\
 SetWindowPlacement, 'SetWindowPlacement',\
 GetMonitorInfoA, 'GetMonitorInfoA',\
 SetWindowLongA, 'SetWindowLongA',\
 SetWindowPos, 'SetWindowPos',\
 MonitorFromWindow, 'MonitorFromWindow'

 import gdi,\
    StretchDIBits, 'StretchDIBits',\
    PatBlt, 'PatBlt'

结论。有人向我指出调用宏破坏了我用于 [BitmapInfo] 的 rax 寄存器,所以这是愚蠢的。修复该问题会导致出现预期的黑屏。

抱歉我对fasm了解不多,我尝试通过C++重现问题:

    void* p = VirtualAlloc(NULL, 512 * 512, MEM_COMMIT, PAGE_READWRITE);
    BITMAPINFO bi{};
    bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bi.bmiHeader.biWidth = 512;
    bi.bmiHeader.biHeight = 512;
    bi.bmiHeader.biPlanes = 1;
    bi.bmiHeader.biBitCount = 32;
    bi.bmiHeader.biCompression = BI_RGB;
    StretchDIBits(hdc, 0, 0, 512, 512, 0, 0, 512, 512, p, &bi, DIB_RGB_COLORS, SRCCOPY);

我尝试使用上面的代码并重现了问题。在我的测试下,我认为分配的内存 space 是不够的。

我修改为:

void* p = VirtualAlloc(NULL, 512 * 512 * 4, MEM_COMMIT, PAGE_READWRITE);

那对我有用。

调用宏破坏了通过 rax 传递的参数。