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 传递的参数。
我有一个简单的 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 传递的参数。