Hello 2 all.
i Have 2 questions ??
i have been reading around on the Internet for a while now, and trying to get assembly programming into my brians, i landed on Randy's A.O.A site http://webster.cs.ucr.edu/Page_win32/WindowsAsmPgm/html/Ch05.html, i tried to duplicate his WndProc routine, got it working, but still don't know if this is the best aproach,there maybe errors in the code, because i'm still wrapped in paper you see :'( (brand new or N00b).Well one things for sure abouth this aproach ( call a Message Handler's proc ) It's kinda keeps winproc from becomming messy.
Ok question 1). What is better seperate calls to Message handlers proc's ,or just jumping to Labels instead ?
question 2) Why doesn't CreateWindowsEx create the button inside my WM_CREATE message handler ?
It does howe ever create the Button outside of the routine e.g just after i create the main window.
It looks like that WM__CREATE proc can't see the static hWnd variale, and this puzzels my mind.
Any help or hints are welcome.
Complete code below.
.586
.model flat,stdcall
option casemap:none
include windows.inc
include user32.inc
include kernel32.inc
include gdi32.inc
include pnglib.inc
include c:\masm32\macros\macros.asm
includelib gdi32.lib
includelib user32.lib
includelib kernel32.lib
includelib pnglib.lib
;******************* EQUATES **************************************************
IDB_BUTTON equ 102
MessProcPointer struct
MessageValue dd ?
MessageHndlr dd ?
MessProcPointer ENDS
;******************* PROTOTYPE DECLARATIONS *************************************
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
CenterScreen proto :DWORD
WM__CLOSE proto :DWORD,:DWORD
WM__COMMAND proto :DWORD,:DWORD
WM__CREATE proto :DWORD,:DWORD
WM__DESTROY proto :DWORD,:DWORD
WM__PAINT proto :DWORD,:DWORD
;******************* INITIALIZED DATA ******************************************
.data
ClassName db "TestProc",0
AppName db "TestProc 1.0 (C)opyright Bigantiman 2005",0
; errLoadRes db "ERROR: could not load resource",0
; errDecode db "ERROR: could not decode PNG",0
; errBitmap db "ERROR: could not create bitmap",0
exitText db "CREDITS ",13,10
db "-------------------------------------------------------",13,10
db "TO DO :PNG lib (C)opyright 2005 Thomas Bleeker (www.madwizard.com)",13,10
db "CenterWindow Routine by Iczelion",13,10
db "TO DO :Music by Bigantiman (C)opyright 2005",13,10,0
btClassName db "BUTTON",0
Dispatch \
MessProcPointer< WM_CREATE, WM__CREATE >
MessProcPointer< WM_COMMAND, WM__COMMAND >
MessProcPointer< WM_DESTROY, WM__DESTROY >
MessProcPointer< WM_CLOSE, WM__CLOSE >
MessProcPointer< 0,0>
;******************* NON INIT DATA ***********************************************
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hdc dd ? ; Handle to video display device context
hWnd dd ?
hButton dd ?
ps PAINTSTRUCT <?> ; Used while painting text.
wc WNDCLASSEX <?>
msg MSG <?>
; PNG LIB --------------------------------------------
; pngInfo PNGINFO <?>
; pngWidth dd ?
; pngHeight dd ?
; hpngBitmap dd ?
; hOldpngBitmap dd ?
; Handle to PNG bitmap:
; hBitmap dd ?
; hBitmapDC dd ?
; hOldBitmap dd ?
;********************* CODE SECTION ***********************************************
.code
; ---------------------------------------------------------------------------
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
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 hInstance
pop wc.hInstance
mov wc.hbrBackground,COLOR_BTNFACE+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
invoke CreateWindowEx,NULL,\
ADDR ClassName,\
ADDR AppName,\
WS_OVERLAPPEDWINDOW and (not ( WS_SIZEBOX or WS_MAXIMIZEBOX)),\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
400,\
500,\
NULL,\
NULL,\
hInst,\
NULL
mov [hWnd],eax ; save our handle to the Window
invoke CenterScreen,[hWnd] ; call the Center Screen Proc
invoke AnimateWindow,[hWnd],1000,AW_BLEND ; Animate the screen
invoke ShowWindow, [hWnd],SW_SHOWNORMAL
invoke UpdateWindow, [hWnd]
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp
;*************** WINDOWS PROCEDURE ***********************************************
WndProc proc hWin:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
mov eax,uMsg ; Copy uMsg into eax
lea edx,Dispatch ; get adress of Dispatch into edx
FORE_EVER:
mov ecx,MessProcPointer.MessageHndlr[edx] ; move Message Handler into ecx
cmp ecx,0 ; Is it end of the Dispatch List
jne CHECK_NEXT ; No check Next
invoke DefWindowProc,hWin,uMsg,wParam,lParam ; Let windows handle non listed Mess
ret
CHECK_NEXT:
cmp eax,MessProcPointer.MessageValue[edx]
jne MOVE_ON
;push [hWnd]
push wParam
push lParam
call ecx
sub ecx,ecx
ret
MOVE_ON:
add edx,SIZEOF MessProcPointer ; point 2 the next element
jmp FORE_EVER
WndProc endp
;************* MESSAGE HANDLERS ****************************************************
WM__CREATE proc wParam:DWORD,lParam:DWORD
invoke CreateWindowEx,0,ADDR btClassName,SADD("&Exit"),WS_CHILD or WS_VISIBLE or BS_PUSHBUTTON,0,0,70,50,[hWnd],IDB_BUTTON,[hInstance],0
mov hButton,eax
mov eax,0
ret 8 ; Pops 2 para off the stack
WM__CREATE endp
WM__COMMAND proc wParam:DWORD,lParam:DWORD
;wParam --> The low-order word contains the button's control identifier.
;wParam --> The high-order word specifies the notification message.
;lParam -->Handle to the button.
; wParam ; [ebp+008]
; lParam ; [ebp+012]
cmp [lParam+2],BN_CLICKED
jne NOT_PRESSED
invoke MessageBoxEx, hWnd,\
SADD("You Pressed the Exit Button Baby"),\
SADD("Message"),\
MB_OK,NULL
invoke PostQuitMessage,NULL
NOT_PRESSED:
mov eax,0
ret 8 ; Pops 2 para off the stack
WM__COMMAND endp
WM__CLOSE proc wParam:DWORD,lParam:DWORD
invoke MessageBoxEx, hWnd, SADD("CEE U LATER "),SADD("Message"),MB_YESNO,0
cmp eax,IDYES
jne @@EXIT_OUT
invoke AnimateWindow, hWnd,900,AW_HIDE or AW_BLEND
invoke DestroyWindow, hWnd
@@EXIT_OUT:
mov eax,0
ret 8 ; Pops 2 para off the stack
WM__CLOSE endp
WM__DESTROY proc wParam:DWORD,lParam:DWORD
invoke MessageBoxEx,hWnd,ADDR exitText,SADD("Message"),MB_OK,0
invoke PostQuitMessage,0
mov eax,0
ret 8 ; Pops 2 para off the stack
WM__DESTROY endp
CenterScreen proc hwnd:HWND
LOCAL DlgHeight:DWORD
LOCAL DlgWidth:DWORD
LOCAL DlgRect:RECT
LOCAL DesktopRect:RECT
invoke GetWindowRect,hwnd,ADDR DlgRect
invoke GetDesktopWindow
mov ecx,eax
invoke GetWindowRect,ecx,addr DesktopRect
push 0
mov eax,DlgRect.bottom
sub eax,DlgRect.top
mov DlgHeight,eax
push eax
mov eax,DlgRect.right
sub eax,DlgRect.left
mov DlgWidth,eax
push eax
mov eax,DesktopRect.bottom
sub eax,DlgHeight
shr eax,1
push eax
mov eax,DesktopRect.right
sub eax,DlgWidth
shr eax,1
push eax
push hwnd
call MoveWindow
ret
CenterScreen endp
end start
A couple of things, for all the attempts I have seen to try and save a few bytes by using different means to manage a WndProc style procedure, the only one that reliably stands the test of time is the OS designed technique that passes 4 DWORD sized variable on the stack to the WndProc.
You will not have the handle for the main windows until AFTER the WM_CREATE has returned so the variable will not be valid until then. What you use is the HANDLE member of the 4 parameters passed to the WndProc and it works fine.
Quote from: hutch-- on April 29, 2005, 01:45:43 AM
A couple of things, for all the attempts I have seen to try and save a few bytes by using different means to manage a WndProc style procedure, the only one that reliably stands the test of time is the OS designed technique that passes 4 DWORD sized variable on the stack to the WndProc.'
Thanks for the Repl Hutch, but i'm not so clear on this answer, should i keep using the calls 2 the Message handler routines, or simply step over to jumps or it really doesn't matter as long as your Winproc runs by the OS designed rules 4 DWORD passes on stack .?
i'm sorry i'm kinda slow. :'(
Quote from: hutch-- on April 29, 2005, 01:45:43 AM
You will not have the handle for the main windows until AFTER the WM_CREATE has returned so the variable will not be valid until then. What you use is the HANDLE member of the 4 parameters passed to the WndProc and it works fine.
ok Got it working with my Old routine check it if you please.
WndProc proc
push ebp
mov ebp,esp
push ebx
push esi
push edi
mov eax,dword ptr [ebp+012] ; Copy Message into eax
lea edx,Dispatch ; get adress of Dispatch into edx
FORE_EVER:
mov ecx,MessProcPointer.MessageHndlr[edx] ; move Message Handler into ecx
cmp ecx,0 ; Is it end of the Dispatch List
jne CHECK_NEXT ; No check Next
invoke DefWindowProc,dword ptr [ebp+008],\ ;hWnd
dword ptr [ebp+012],\ ;uMsg
dword ptr [ebp+016],\ ;wParam
dword ptr [ebp+020] ;lParam
jmp FINISH
CHECK_NEXT:
cmp eax,MessProcPointer.MessageValue[edx]
jne MOVE_ON
push dword ptr [ebp+008] ; hWnd
push dword ptr [ebp+016] ; wParam
push dword ptr [ebp+020] ; lParam
call ecx
sub ecx,ecx
FINISH:
pop edi
pop esi
pop ebx
pop ebp
ret 16
MOVE_ON:
add edx,SIZEOF MessProcPointer ; point 2 the next element
jmp FORE_EVER
WndProc endp
And the WM__CREATE proc looks like this now
WM__CREATE proc
invoke CreateWindowEx,0,ADDR btClassName,SADD("&Exit"),WS_CHILD or WS_VISIBLE or BS_PUSHBUTTON,0,0,70,50,dword ptr [ebp+008],IDB_BUTTON,[hInstance],0
mov hButton,eax
mov eax,0
ret 12 ; Pops 3 para off the stack
WM__CREATE endp
Trouble is I am not sure what you are trying to do. Have a look at a standard WndProc.
WndProc proc hWin:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD ; 4 args on the stack
; process messages here, .IF block or CMP / Jxx or jump table
invoke DefWindowProc,hWin,uMsg,wParam,lParam ; default processing
ret
WndProc endp
Of the message you can process, the WM_CREATE is called DURING the external CreateWindowEx() function call in your startup code. Thats why the return value from it does not work until it has returned so you must use the 1st stack parameter as the window handle if you need it.
When you have a message the does not want the default processing like WM_PAINT in some circumstances, you return BEFORE the DefWindowProc() placing the return value you require in EAX.
You seem to be doing a lot more work than you need, there is no point tying up EAX for the message comparison when a CMP works fine on a memory operand, in this instance the 2nd stack parameter. The loop processing you are using is in fact very slow for message processing and it has no advantage over a sequential fall through comparison done in an .IF block.
If you can see an advantage in faster processing, a jump table is far faster than anything else but even when you code it, you cannot see the difference in the app.
Quote from: bigantiman on April 29, 2005, 01:33:23 AM
Ok question 1). What is better seperate calls to Message handlers proc's ,or just jumping to Labels instead ?
Same thing. When MASM finds a "proc", it replaces it with a label and some extra code to set up the stack frame.
Quote from: hutch-- on April 29, 2005, 07:42:18 AM
Trouble is I am not sure what you are trying to do.
Hihi you got me there.
Well i'll try to make it short.
After reading this
Quote
The important thing to notice is that commonly used message values aren't necessarily contiguous (indeed, they can be widely spaced apart) and there are a lot of them. This pretty much precludes using a switch/case statement (or an assembly equivalent - a jump table) because the corresponding jump table would be huge. Since few window procedures process more than a few dozen messages, many application's window procedures just use a if..else if chain to compare uMsg against the set of messages the window procedure handles; therefore, a typical window procedure often looks somewhat like the following:
procedure WndProc( hwnd: dword; uMsg:dword; wParam:dword; lParam:dword );
@stdcall;
@nodisplay;
@nostackalign;
begin WndProc;
// uMsg contains the current message Windows is passing along to
// us. Scan through the "Dispatch" table searching for a handler
// for this message. If we find one, then call the associated
// handler procedure. If we don't have a specific handler for this
// message, then call the default window procedure handler function.
mov( uMsg, eax );
if( eax = w.WM_DESTROY ) then
w.PostQuitMessage( 0 ); // Do this to quit the application
elseif( eax = w.WM_PAINT ) then
<< At this point, do whatever needs to be done to draw the window >>
else
// If an unhandled message comes along,
// let the default window handler process the
// message. Whatever (non-zero) value this function
// returns is the return result passed on to the
// event loop.
w.DefWindowProc( hwnd, uMsg, wParam, lParam );
endif;
end WndProc;
There are two problems with this approach. The major problem with this approach is that you wind up processing all your application's messages in a single procedure. Although the body of each if statement could, in theory, call a separate function to handle that specific message, in practice what really happens is the program winds up putting the code for a lot of the messages directly into the window procedure. This makes the window procedure really long and more difficult to read and maintain. A better solution would be to call a separate procedure for each message type.
i tried to create a copy of his WndProc aproach (wich you can read on his site) http://webster.cs.ucr.edu/Page_win32/WindowsAsmPgm/html/Ch05.html) routine wich calls sepperate individual Message Proc's.
It looked good in a HLA program.
So i tried to create the exact copy of it in MASM ( but this was reinventing the wheel )..
The main goal here was to understand what was going on in WinProc and Creating a Clean not messy WndProc.
I know now that this could be done just jumping or calling, and ill get the same effect ( a clean not messy WndProc ).
It's kinda confusing because i was told Not 2 use the .IF .ENDIF routines as a newbie, because it wil hide the real innnerworking of the code.
WndProc hWin:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
.if uMsg==WM_CREATE ; WM_CREATE
jmp WM__CREATE
Or i can do this
WndProc proc hWin:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
.if uMsg==WM_CREATE ; WM_CREATE
invoke WM__CREATE ; The Invoke way
;or
call WM_CREATE ; or the Old Fasion way
But thanks for the answers. :U
There are at least four "clean" approaches to message handling.
Indexed address table - creates a huge table. Hutch experimented with this approach, using a simple way to initialize the table at runtime.
A search table - much smaller table, also known as dispatch table. Uses a loop to search for messages and their corresponding handler. Easy to modify.
Standard IF-THEN-ELSEIF code - uses CMP and JNE (or JNZ). If you don't have "anonymous" labels, this is a pain because you need to search backwards to find the jump label that needs to change when you insert or delete code.
Grouped CMP-JE at beginning - this is a fairly standard coding style for microcontrollers (in embedded systems) because, for many of these simpler processors, it is faster to "fall through" to the next code than it is to jump to the alternate location. In addition, it's just as easy to modify as a search table.
Example:
cmp eax,WM_CREATE
je OnCreate
cmp eax,WM_PAINT
je OnPaint
cmp eax,WM_DESTROY
je OnDestroy
A few of us have also performed a jump to subroutine in a way that reuses the original parameters.
WndProc: ; don't use PROC, avoid setting up stack frame
; As per the Intel ABI, don't touch EBP, EBX, ESI, or EDI
; And don't touch ESP
mov eax,[esp+8] ; get uMsg using ESP instead of EBP
; EBP is not stacked, so second parameter (uMsg) is at +8, not +12
cmp eax,WM_CREATE
je OnCreate
cmp eax,WM_PAINT
je OnPaint
cmp eax,WM_DESTROY
je OnDestroy
jmp DefWindowProc
; Write message handlers as if they were the WndProc's
OnCreate PROC hwnd: DWORD, uMsg: DWORD, wParam: DWORD, lParam: DWORD
;...
ret
ENDP
Quote from: tenkey on April 29, 2005, 07:58:54 PM
There are at least four "clean" approaches to message handling.
A few of us have also performed a jump to subroutine in a way that reuses the original parameters.
Thanks Tenkey totaly get it now,, and add me to the a few of us list :bg :U