                   STANDARDIZING Win32 CALLBACK PROCEDURES
                             by Jeremy Gordon
                              JG at JGnet.co.uk
                         http://www.GoDevTool.com

This short article describes my preferred method for coding CALLBACK procedures
in a large assembler program for Windows 32.  First I describe what Win32
callback procedures are, and then get down to some code.

At run time the Win32 system will call your program on a regular and frequent
basis.  The procedures you supply for the system to call are called CALLBACK
procedures.  Here are examples of when these are used:-
1. To manage a window you created.  In this case the system will send many
   messages to the Window Procedure for the window.  The Window Procedure is
   the code label you provide when you register your window class (by calling
   RegisterClass).  For example the message WM_SIZE is sent by the system
   when the window is resized.
2. To inform the owner of a child window of events in the child window.  For
   example WM_PARENTNOTIFY (with a notify code) is sent to the Window
   Procedure of the owner of a window when the child window is being created
   or destroyed, or if the user clicks a mouse button while the cursor is
   over the child window.
3. To inform the owner of a common control of events in the control.  For
   example if you create a button owned by your window the Window Procedure
   for that window receives BN_CLICKED messages if the button is clicked.
4. Messages sent to a dialog you have created.  These are messages relating
   to the creation of the dialog and of the various controls.  The dialog
   procedure is informed of events in the controls.
5. If you "Superclass" or "Subclass" a common control, you receive messages
   for that common control like a hook procedure but your window procedure
   has the responsibility of passing them on to the control.
6. If you create "Hook" procedures you can intercept messages about to be sent
   to other windows.  The system will call your hook procedure and will pass
   the message on only when your hook procedure returns.
7. You can ask the system to provide your program with information to be sent
   to a CALLBACK procedure.  Examples are EnumWindows (enumerate all top-level
   windows) or EnumFonts (enumerate all available fonts).

In cases 1 to 5 above, just before the system calls the CALLBACK procedure,
it PUSHES 4 dwords on the stack (ie. 4 "parameters").  Traditionally the
names given to these parameters are:-
           hWnd = handle of window being called
           uMsg = message number
           wParam = a parameter sent with the message
           lParam = another parameter sent with the message.

The number of parameters sent to hook procedures and emumeration
callbacks varies - see the Window SDK.

Since your Window (or Dialog) procedure will need to react in a certain
way depending on the message being sent, your code will need to divert
execution to the correct place for a particular message.

"C" programmers have the advantage of being able to code this simply,
using "switch" and "case".

Assembler programmers use various techniques.  Perhaps the worst if there are
a lot of messages to handle is the chain of compares, eg. (in GoAsm format):-
           MOV EAX,[EBP+0Ch]        ;get message number
           CMP EAX,1h               ;see if WM_CREATE
           JNZ >L2                  ;no
           XOR EAX,EAX              ;ensure eax is zero on exit
           JMP >L32                 ;finish
           L2:
           CMP EAX,116h             ;see if WM_INITMENU
           JNZ >L4                  ;no
           CALL INITIALISE_MENU
           JMP >L30                 ;correct exit code
           L4:
           CMP EAX,47h              ;see if WM_WINDOWPOSCHANGED
           JNZ >L8
           and so on ........

To avoid these long chains, assembler programmers have developed various
techniques.  You will have seen many of these in sample code around Win32
assembler web sites and in the asm journal, using conditional jumps, macros
or table scans.  I do not wish to compare these various methods, merely to put
forward my own current favourite, which I believe has these advantages:-
1. It works on all assemblers
2. It is modular, ie. the code for each window can be concentrated in a
   particular part of your source code
3. It is easy to follow from the source code what message causes what result
4. The same function can easily be called from within different window
   procedures

My method results in a very simple Window Procedure as follows (GoAsm format):-

           WndProc:
           MOV EDX,OFFSET MAINMESSAGES
           CALL GENERAL_WNDPROC
           RET 10h

where the messages and functions (specific to this particular window
procedure) are set out in a table such as this:-

;----------------------------------------------------------
DATA SECTION           ;assembler to put following in data section
;--------------------------- WNDPROC message functions
MAINMESSAGES DD (ENDOF_MAINMESSAGES-$-4)/8        ;=number to be done
           DD  312h,HOTKEY,116h,INITMENU,117h,INITMENUPOPUP,11Fh,MENUSELECT
           DD  1h,CREATE,2h,DESTROY, 410h,OWN410,411h,OWN411
           DD  231h,ENTERSIZEMOVE,47h,WINDOWPOSCHANGED,24h,GETMINMAXINFO
           DD  1Ah,SETTINGCHANGE,214h,SIZING,46h,WINDOWPOSCHANGING
           DD  2Bh,DRAWITEM,0Fh,PAINT,113h,TIMER,111h,COMMAND
           DD  104h,SYSKEYDOWN,100h,KEYDOWN,112h,SYSCOMMAND
           DD  201h,LBUTTONDOWN,202h,LBUTTONUP,115h,SCROLLMESS
           DD  204h,RBUTTONDOWNUP,205h,RBUTTONDOWNUP
           DD  200h,MOUSEMOVE,0A0h,NCMOUSEMOVE,20h,SETCURSORM
           DD  4Eh,NOTIFY,210h,PARENTNOTIFY,86h,NCACTIVATE,6h,ACTIVATE
           DD  1Ch,ACTIVATEAPP
ENDOF_MAINMESSAGES:           ;label used to work out how many messages
;----------------------------------------------------------
CODE SECTION                  ;assembler to put following in code section
;----------------------------------------------------------

and where each of the functions here are procedures, for example:-

           CREATE:
           XOR EAX,EAX             ;ensure zero and nc return
           RET

and where GENERAL_WINDPROC is as follows:- 

           GENERAL_WNDPROC:
           PUSH EBP
           MOV EBP,[ESP+10h]       ;get uMsg in ebp
           MOV ECX,[EDX]           ;get number of messages to do
           ADD EDX,4               ;jump over size dword
           L33:
           DEC ECX
           JS >L46                 ;s=message not found
           CMP [EDX+ECX*8],EBP     ;see if its the correct message
           JNZ L33                 ;no
           MOV EBP,ESP
           PUSH ESP,EBX,EDI,ESI    ;save registers as required by Windows
           ADD EBP,4               ;allow for the extra call to here
           ;now [EBP+8]=hWnd,[EBP+0Ch]=uMsg,[EBP+10h]=wParam,[EBP+14h]=lParam
           CALL [EDX+ECX*8+4]      ;call correct procedure for the message
           POP ESI,EDI,EBX,ESP
           JNC >L48                ;nc=don't call DefWindowProc eax=exit code
           L46:
           PUSH [ESP+18h],[ESP+18h],[ESP+18h],[ESP+18h]  ;ESP changes on push
           CALL DefWindowProcA
           L48:
           POP EBP
           RET

NOTES:
1. Instead of giving the actual message value, you can, of course, give
   the name of an EQUATE.  For example (in GoAsm syntax)
           WM_CREATE EQU 1h 
           or if you prefer
           WM_CREATE=1h 
           or if you prefer
           #define WM_CREATE 1h
   enables you to use WM_CREATE,CREATE instead of 1h,CREATE if you wish.
2. It is tempting to keep the message table in the CODE SECTION.  This is
   perfectly possible because the only difference to the Win32 system between
   the code section and the data section is that the code section area of
   memory is marked read only, whereas the data section is read/write.
   However, you should expect some loss of performance if you do this because
   the processor is designed to read data more quickly from the data section.
   I performed some tests on this and found that having the table in the code
   section rather than the data section slowed the code considerably:-
               486 processor - 22% to 36% slower
         AMD-K6-3D processor - 78% to 193% slower
   These tests were carried out on a table of 60 messages and the range here
   relates to how quickly in the table scan the message was found.
3. The procedure names must not be the names of API imports to avoid
   confusion!  For example change SETCURSOR slightly to avoid confusion
   with the API SetCursor.
4. If a function returns c (carry flag set) the window procedure will call
   DefWindowProc.  An nc return (carry flag not set) will merely return to
   the system with the return code in eax.  (Some messages must be dealt with
   in this way).
5. You can send a parameter of your own to GENERAL_WNDPROC using EAX.
   This is useful if you wish to identify a particular window.
   For example:-
           SpecialWndProc:
           MOV EAX,OFFSET hSpecialWnd
           MOV EDX,OFFSET SPECIALWND_MESSAGES
           CALL GENERAL_WNDPROC
           RET 10h
6. The ADD EBP,4 just before the call to the function is to ensure that
   EBP points to the parameters the stack in the same way as if the window
   procedure had been entered normally.  This is intended to ensure that
   the function will be compatible if called by an ordinary window procedure
   written in assembler, for example:-
           WndProc:
           PUSH EBP
           MOV EBP,ESP 
          ;now [EBP+8]=hWnd,[EBP+0Ch]=uMsg,[EBP+10h]=wParam,[EBP+14h]=lParam
7. A standardized procedure for dealing with messages to a dialog procedure
   can also be created in the same way, except that it should return TRUE
   (eax=1) if the message is processed and FALSE (eax=0) if it is not, without
   calling DefWindowProc. The same coding method can be applied to hooks and
   to enumerator CALLBACKS although these will vary.  

[Adapted from article first published in Asm Journal No. 5]