call a Message handler or Jump 2 a message Handler .??

Started by bigantiman, April 29, 2005, 01:33:23 AM

Previous topic - Next topic

bigantiman

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


hutch--

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.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

bigantiman

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


hutch--

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.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

AeroASM

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.

bigantiman

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

tenkey

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
A programming language is low level when its programs require attention to the irrelevant.
Alan Perlis, Epigram #8

bigantiman

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