I've managed to write a simple Windows message loop in pure assembly. There's one thing that gave me a slight headache on the way there, and now I'm wondering why it is so.
This is my WndProc function (the one I specify in WNDCLASSEX.lpfnWndProc):
_WndProc: ;LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
PUSH EBP
MOV EBP,ESP
CMP DWORD [EBP+12d],WM_CLOSE ;switch msg (UINT, second param)
JNZ eval_wm_destroy
PUSH DWORD [EBP+8d]
CALL _DestroyWindow#4
POP EBP
RET
eval_wm_destroy:
CMP DWORD [EBP+12d],WM_DESTROY
JNZ default_behavior
PUSH 0
CALL _PostQuitMessage#4
POP EBP
RET
default_behavior:
PUSH DWORD [EBP+20]
PUSH DWORD [EBP+16]
PUSH DWORD [EBP+12]
PUSH DWORD [EBP+8]
CALL _DefWindowProcA#16
POP EBP
RET
;---
What I find slightly weird is that I'm not supposed to clean up the stack arguments in WndProc. The code posted above works, whereas if I try to do what I thought I should, like this:
CMP DWORD [EBP+12d],WM_CLOSE ;switch msg (UINT, second param)
JNZ eval_wm_destroy
PUSH DWORD [EBP+8d]
CALL _DestroyWindow#4
POP EBP
ADD ESP,16d
RET
I get a crash. Such behavior suggests that the window procedure is not supposed to clean up after itself, making it... _cdecl?
Is that the case?
WndProc is supposed to use WINAPI, which is stdcall - that is, the function itself is supposed to clean up any arguments.
The reason it works anyway for you is probably that the calling scope doesn't rely on ESP being in the right place after the call and when it itself returns the stack is 'repaired' when ESP is reinitialized with the saved value from entry in EBP.
Related
This is a basic win32 program that I found online. So far I get everything, but what I don't get are those two lines:
push hInstance
pop wc.hInstance
Can someone explain to me what they do and if there is another way to do whatever is done by them using another instruction.
I tried to use google and other documentation and they explained very well what the push and pop instructions do, but I can't fit my understanding of them in the context of this program.
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib ; calls to functions in user32.lib and kernel32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
.DATA ; initialized data
ClassName db "SimpleWinClass",0 ; the name of our window class
AppName db "Our First Window",0 ; the name of our window
.DATA? ; Uninitialized data
hInstance HINSTANCE ? ; Instance handle of our program
CommandLine LPSTR ?
.CODE ; Here begins our code
start:
invoke GetModuleHandle, NULL ; get the instance handle of our program.
; Under Win32, hmodule==hinstance mov hInstance,eax
mov hInstance,eax
invoke GetCommandLine ; get the command line. You don't have to call this function IF
; your program doesn't process the command line.
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT ; call the main function
invoke ExitProcess, eax ; quit our program. The exit code is returned in eax from WinMain.
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX ; create local variables on stack
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX ; fill values in members of wc
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInstance
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc ; register our window class
invoke CreateWindowEx,NULL,\
ADDR ClassName,\
ADDR AppName,\
WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
NULL,\
NULL,\
hInst,\
NULL
mov hwnd,eax
invoke ShowWindow, hwnd,CmdShow ; display our window on desktop
invoke UpdateWindow, hwnd ; refresh the client area
.WHILE TRUE ; Enter message loop
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam ; return exit code in eax
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY ; if the user closes our window
invoke PostQuitMessage,NULL ; quit our application
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam ; Default message processing
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
It's pushing hInstance onto the stack, and then popping it into the memory location of wc.hInstance.
The programmer could have equivalently written:
mov eax, hInstance
mov wc.hInstance, eax
if they knew they didn't need to preserve EAX.
I have too much time on my hands and decided to write a simple program in MASM32 for no good reason. However, I can't get my SetWindowPos call to the WinApi to work. Here's the relevant code bit:
invoke GetForegroundWindow
mov ecx,eax
invoke GetWindowLong, ecx, GWL_EXSTYLE
AND eax,WS_EX_TOPMOST ; check if window-
.if eax==0 ; is already on top
mov edx,HWND_TOPMOST; nope, make it on top
.else
mov edx,HWND_NOTOPMOST; yes, make it not on top
.endif
push ecx ; window handle
push edx ; placement handle
push edi ; edi=0, someone told me this is faster
push edi
push edi
push edi
push 0043h ; SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE
call SetWindowPos
cmp eax,0
jne mLoop1 ; jump to message loop
invoke HandleError, addr appname ; make message box with error message
; ^^^^^^^^ this shows "Invalid Window Handle"
jmp mLoop1 ; jump to message loop
I'm sure I'm missing somehting very obvious, but with my limited knowledge of assembly I have no idea what.
The program compiles fine, but it fails to create the main window. Specifically, CreateWindowEx fails and prints "Failed to create window".
Would anyone happen to know what I'm doing wrong? I'm following Kip Irvine's book on assembly almost exactly, but it seems I'm missing something.
EDIT: I updated the code based on the recommendations. Now the program fails to register the window class, and the specific error is that a "Parameter is incorrect". I looked over the parameters of my WNDCLASSEX struct, and couldn't fine anything wrong.
EDIT2: I removed the "Ex" from WNDCLASS and RegisterClass and the window shows up and works fine now. So I guess it was some kind of weird redefinition or inconsistency of structs and functions in the masm32rt library?
INCLUDE \masm32\include\masm32rt.inc
.data
windowName BYTE "ASM Windows App",0
className BYTE "ASMWin",0
MainWinClass WNDCLASSEX <NULL,CS_HREDRAW + CS_VREDRAW,WinProc,NULL,NULL,NULL,NULL,NULL,COLOR_WINDOW+1,NULL,className,NULL>
windowHandle DWORD ?
hInstance DWORD ?
.code
WinMain PROC
; Get a handle to the current process.
INVOKE GetModuleHandle, NULL
mov hInstance, eax
mov MainWinClass.hInstance, eax
; Check if the handle was received.
.IF eax == 0
pushad
print "Failed to get handle on current process"
popad
call ErrorHandler
jmp ExitProgram
.ENDIF
; Load the program's icon and cursor.
INVOKE LoadIcon, NULL, IDI_APPLICATION
mov MainWinClass.hIcon, eax
INVOKE LoadCursor, NULL, IDC_ARROW
mov MainWinClass.hCursor, eax
; Create the window class.
INVOKE RegisterClassEx, ADDR MainWinClass
; Check if the class was registered.
.IF eax == 0
pushad
print "Failed to register class."
popad
call ErrorHandler
jmp ExitProgram
.ENDIF
; Create the window.
INVOKE CreateWindowEx, 0, ADDR className, ADDR windowName, WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL
; Check if window was created successfully.
.IF eax == 0
pushad
print "Failed to create window"
popad
call ErrorHandler
jmp ExitProgram
.ENDIF
; Save the window handle and use it to show the window.
mov windowHandle, eax
INVOKE ShowWindow, windowHandle, SW_SHOW
INVOKE UpdateWindow, windowHandle
; Message Loop
;MessageLoop:
; INVOKE GetMessage, ADDR msg, NULL, NULL, NULL
; INVOKE DispatchMessage, ADDR msg
; jmp MessageLoop
ExitProgram:
;CALL ReadChar
INVOKE ExitProcess, 0
WinMain ENDP
; Window Procedure
WinProc PROC,
hWnd:DWORD, localMsg:DWORD, wParam:DWORD, lParam:DWORD
mov eax, localMsg
.IF eax == WM_CREATE
;call WriteString
jmp WinProcExit
.ELSEIF eax == WM_CLOSE
;call WriteString
jmp WinProcExit
.ELSE
;call WriteString
INVOKE DefWindowProc, hWnd, localMsg, wParam, lParam
jmp WinProcExit
.ENDIF
WinProcExit:
ret
WinProc ENDP
ErrorHandler PROC
.data
pErrorMsg DWORD ?
messageID DWORD ?
.code
INVOKE GetLastError
mov messageID, eax
; Get the corresponding message string.
INVOKE FormatMessage, FORMAT_MESSAGE_ALLOCATE_BUFFER + \
FORMAT_MESSAGE_FROM_SYSTEM,NULL,messageID,NULL,
ADDR pErrorMsg,NULL,NULL
; Display the error message.
INVOKE MessageBox, NULL, pErrorMsg, NULL,
MB_ICONERROR+MB_OK
; Free the error message string.
INVOKE LocalFree, pErrorMsg
ret
ErrorHandler ENDP
END WinMain
WNDCLASSEX requires WNDCLASSEX.cbSize to be set. The mistake I made was that I assumed it could be NULL.
So I added this piece of code before registering the class:
; Initializing other parameters of the window class.
mov eax, SIZEOF MainWinClass
mov MainWinClass.cbSize, eax
In addition, Kip Irvine's functions cause errors when used along side some functions of the user interface section of the Windows API. I'm not exactly sure why that happens but it could be that some register values are changed around.
I am trying to create a window in x86 assembly with masm32 using the CreateWindowEx API. I have gotten my code to have no compile-time errors or anything of the sort- it compiles just fine. Yet when I run the exe, nothing happens. I don't see any obvious errors, and I have practically copied the code out of Iczelion's Win32 Tutorial (Part 3 - A Simple Window). What is wrong with it?
Here is my code:
.386
.model flat, stdcall
option casemap :none
WinMain proto :DWORD,:DWORD, :DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib
.data
ClassName db "Testwin", 0
AppName db "Testing Window", 0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
.code
start:
push NULL
call GetModuleHandle
mov hInstance,eax
call GetCommandLine
mov CommandLine, eax
push SW_SHOWDEFAULT
push CommandLine
push NULL
push hInstance
call WinMain
push eax
call ExitProcess
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE, CmdLine:LPSTR,CmdShow:DWORD
; local vars:
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
; defining the window:
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
;create the window
invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInst,NULL
invoke ShowWindow,hwnd,SW_SHOWNORMAL
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
cmp uMsg, WM_DESTROY
jne _next
invoke PostQuitMessage, NULL
_next:
WndProc endp
end start
Where have I gone wrong? I suspect it has something to do with CreateWindowEx, considering it takes 12 parameters, most of which I don't understand.
Thanks in advance.
I believe you have not assigned the window handle returned by CreateWindowEx to the hwnd variable.
So add the following line after invoke CreateWindowEx and before invoke ShowWindow -
mov hwnd, eax
We do not compile anything when using Assebmly! We Assemble and Link.
This is not C or any other high level language, you do not need WinMain.
The biggie, where is your message loop
After your CreateWindowEx and ShowWindow, you need something like this right after that:
.while TRUE
invoke GetMessage,addr msg,NULL,0,0
.break .if !eax
;invoke IsDialogMessage,hModelessDialog,addr msg
;.if !eax
;invoke TranslateAccelerator,hWnd,hAccel,addr msg
;.if !eax
invoke TranslateMessage,addr msg
invoke DispatchMessage,addr msg
;.endif
;.endif
.endw
You are also missing ret at the end of your procs
In response to my question about Windows API's, I have successfully gotten it to work. My question is in regards to this code:
push STD_OUTPUT_HANDLE
call GetStdHandle
push NULL
push offset other
push mlen
push offset msg
push eax
call WriteConsole
push 0
call ExitProcess
This code is supposed to print the value of msg. Why does one need to do:
a)
push STD_OUTPUT_HANDLE
call GetStdHandle
push NULL
And:
b)
push offset other
push mlen
push offset msg
push eax
I am just wondering what the need is for getting a StdHandle and pushing offsets.
Thanks in advance,
Progrmr
Look at the definition of WriteConsole. The NULL is the last argument of the function, the lpReserved argument. Arguments are pushed in right-to-left order. The first function argument is the console handle, the one you got from GetStdHandle and you pass by pushing eax.
So properly commenting the assembly code:
push STD_OUTPUT_HANDLE ; GetStdHandle nStdHandle argument
call GetStdHandle ; eax = Console handle
push NULL ; lpReserved = null
push offset other ; lpNumberOfCharsWritten = pointer to "other"
push mlen ; nNumberOfCharsToWrite = length of "msg"
push offset msg ; lpBuffer = pointer to "msg"
push eax ; hConsoleOutput = console handle from GetStdHandle
call WriteConsole ; Write string
push 0 ; exit code = 0
call ExitProcess ; terminate program