The WndProc (window procedure) is central to Windows programming. Each application window has a WndProc. The WndProc acts as a "callback" procedure, that is the system (or other code) calls the WndProc with messages. Four dwords are sent on each call. The WndProc's job is to call the correct code to suit the message, to call the default system procedure DefWndProc if necessary to restore the stack having removed the four dwords and return to the caller. You can achieve this in various ways. Which method you choose depends on whether the WndProc needs to keep local data on the stack and how many messages need to be processed. Here are 3 ways to write the WndProc in assembler. Each way works well and you can view the 3 windows made in each case by running the tests in Testbug.
The standard WndProcThis method emulates the type of code you would expect from compiled "C" coding. It makes a stack frame for each message, so that the code can use EBP locally to access the parameters sent by the caller as well as local data on the stack. The breakpoints are STANDARD_WndProc and WndProcStandard.
Here is the code which establishes the class for the window and
makes the window. INITIALISE_WNDCLASS sets ebx to the WNDCLASS structure
and zeroes it.
STANDARD_WndProc: CALL INITIALISE_WNDCLASS ;get ready for all windows MOV D[EBX],01h+02h+40h ;CS_VREDRAW+CS_HREDRAW+CS_CLASSDC MOV D[EBX+4],ADDR WndProcStandard MOV D[EBX+24h],ADDR STANDARD_CLASSNAME PUSH EBX ;address of structure with window class data CALL RegisterClassA ;register a window class for subsequent use PUSH 0,[hInst],0,[hWnd] ;owner=main window PUSH 200D ;height PUSH 320D ;width PUSH 50D,50D ;position y then x PUSH 90000000h +0C00000h+40000h +80000h +20000h +10000h ;(POPUP+VISIBLE)+CAPTION+SIZEBOX+SYSMENU+MINIMIZEBOX+MAXIMIZEBOX PUSH 'Window using standard WndProc' PUSH ADDR STANDARD_CLASSNAME ;class PUSH 0 ;extended window style CALL CreateWindowExA ;make window, returning handle in EAX RETHere is the window procedure itself now. Because EBP keeps the value of ESP and restores it later, it is possible to make an area on the stack to accept local data by reducing ESP by the required number of dwords, and then the data can be addressed by using [EBP-value]. Note also that the ebx, edi and esi registers are saved and restored. All Windows APIs do this and your WndProc should too. Just before the return the 4 parameters are removed from the stack by the RET 10h instruction. Note also that a message value 411h is posted to the main window when this window is destroyed, and passes in this message (in wParam) the name of the window class. The main window procedure calls UnregisterClass and removes the class. Using PostMessage on the very last message received by this window ensure that the removal of the class is the very last thing that happens to this window! Obviously the sequence of events is important here, but it is maintained and wholly predictable for one important reason: the system uses the same thread throughout. Therefore only one thing can happen at once. Here, PostMessage has been called, but nothing happens until the thread returns back to the system (note: this is why SendMessage is not used here!).
WndProcStandard: PUSH EBP MOV EBP,ESP ;********** make area for local data on the stack if required PUSH EBX,EDI,ESI ;save registers as required by Windows ;now [EBP+8]=hWnd, [EBP+0Ch]=uMsg, [EBP+10h]=wParam, [EBP+14h]=lParam, MOV EAX,[EBP+0Ch] ;uMsg CMP EAX,01h ;see if WM_CREATE JZ >L9 ;yes no must return zero CMP EAX,2 ;see if WM_DESTROY JNZ >L6 ;no PUSH 0,ADDR STANDARD_CLASSNAME,411h,[hWnd] ;post 411h message CALL PostMessageA ;to remove the class JMP >L9 L6: PUSH [EBP+14h],[EBP+10h],[EBP+0Ch],[EBP+8h] CALL DefWindowProcA JMP >L10 L9: XOR EAX,EAX L10: POP ESI,EDI,EBX MOV ESP,EBP POP EBP RET 10h
The popping WndProc
The code for setting up the class and making the
window is the same as for the standard window, but the window procedure
is different. Here the parameters sent by the caller to the window
procedure are not kept on the stack but are put into data symbols.
This is nice in a small program, but if you have more than one window
procedure you may have trouble keeping track of all the names, since
obviously each name must be unique to each window. This is because
it is possible that a message might be sent to another window before
this window procedure has returned. This might be as a result of some action within this window procedure or as a result of the call to DefWindowProc. For the same reason you would not dare use this code if windows are sharing the same window procedure. In that case it would be essential to allow each window to have its own stack frame (parameters kept on a particular part of the stack).
The breakpoints are POPPER_WndProc and WndProcPopper.
WndProcPopper:
POP EAX ;get caller
POP [hWndPopper],[uMsgPopper],[wParamPopper],[lParamPopper]
PUSH EAX ;push caller ready to RET at end
PUSH EBX,EDI,ESI ;save registers as required by Windows
MOV EAX,[uMsgPopper] ;uMsg
CMP EAX,01h ;see if WM_CREATE
JZ >L9 ;yes no must return zero
CMP EAX,2 ;see if WM_DESTROY
JNZ >L6 ;no
PUSH 0,ADDR POPPING_CLASSNAME,411h,[hWnd] ;post 411h message
CALL PostMessageA ;to remove the class
JMP >L9
L6:
PUSH [lParamPopper],[wParamPopper],[uMsgPopper],[hWndPopper]
CALL DefWindowProcA
JMP >L10
L9:
XOR EAX,EAX
L10:
POP ESI,EDI,EBX
RET
The table WndProc
I call this the table WndProc here, because it uses a table of message values which it reads from to find the correct procedure to call for each message. This is the WndProc method I use most of the time since it is so easy to add and delete messages during program development. The breakpoints are TABLE_WndProc and WndProcTable.
First the message table itself (which is in the data section):-
;*******************************************************************
DATA SECTION
;*******************************************************************
;message value first then the function to be called
;returns from each function c=go to DefWindowProc, nc=return code in eax
TABLEMESSAGES DD (ENDOF_TABLEMESSAGES-$-4)/8 ;=number to be done
DD 1h,TABLECREATE,2h,TABLEDESTROY
ENDOF_TABLEMESSAGES: ;label used to work out how many messages
Now the two functions in the table which in this example are limited to functions called on the messages WM_CREATE (value 1h) and WM_DESTROY (value 2h):-
;*******************************************************************
CODE SECTION
;*******************************************************************
TABLECREATE:
XOR EAX,EAX ;return zero to make window
RET
;
TABLEDESTROY:
PUSH 0,ADDR TABLE_CLASSNAME,411h,[hWnd] ;post 411h message
CALL PostMessageA ;to remove the class
XOR EAX,EAX ;exit code zero - dont call DefWndProc
RET
Now this is the actual WndProc itself. GENERAL_WNDPROC can be shared between as many WndProc's as you like since all data is kept on the stack. If the WndProc needs to pass information to GENERAL_WNDPROC this can be done in the eax register which is not otherwise used.
WndProcTable:
MOV EDX,ADDR TABLEMESSAGES
CALL GENERAL_WNDPROC
RET 10h
And here is the GENERAL_WNDPROC code. Note that when the call is actually made to the function which deals with the message, EBP is at the same place on the stack relative to the caller as in the standard WndProc. This enables all functions to be shared if required between the two types of WndProc:-
GENERAL_WNDPROC: ;eax can be used to convey information to the call
PUSH EBP ;use ebp to avoid using eax which may hold information
MOV EBP,[ESP+10h] ;uMsg
MOV ECX,[EDX] ;get number of messages to do
ADD EDX,4 ;jump over size dword
L33:
DEC ECX
JS >L46
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 the correct procedure for the message
POP ESI,EDI,EBX,ESP
JNC >L48 ;nc=return value in eax - don't call DefWindowProc
L46:
PUSH [ESP+18h],[ESP+18h],[ESP+18h],[ESP+18h] ;allowing for change of ESP
CALL DefWindowProcA
L48:
POP EBP
RET
 More information about this standardised callback sample code