Windows Event Handler in MASM

Tight_Coder_Ex 0 Tallied Votes 811 Views Share

This procedure dispatches application defined event handlers and alternately executes default process for both standard or sub/super classed windows.

Map is defined as follows. In most cases I define this table in .data, but in can be in .const also. I choose writeable memory as application can redefine characteristics.

MWMap	            dd	0					; Address of sub or super class default proc
		dw	(MWEnd - MWMap - 6) / 6		; Determine number of messages in map
		dw	WM_DESTROY
		dd	QuitApp
		dw	WM_CREATE
		dd	CMainWnd
  MWEnd	equ	$

Entry point of windows procedure requires only two instructions

MWndProc		proc

	  		mov	esi, offset MWMap			; Point to message map for this window
	  		jmp	ScanMap
	    
  MWndProc		endp

By whatever means you choose to intialize WNDCLASSEX then entry point need only be specified as the 3 parameter

Wc		WNDCLASSEX	< 30h, CS_HREDRAW or CS_VREDRAW, MWndProc, 0, 0,\
				 0, 0, 0, COLOR_APPWORKSPACE + 1, NULL, AppName, 0>

Here is an example of the actual handler

CMainWnd		proc	uses	ebx edi
  
  		LOCAL	Rc:RECT
  		
  			lea	ebx, Rc
  			mov	edi, [esi]
  			
  			invoke	GetClientRect, dword ptr [esi], ebx	; Get client windows metrics
  			invoke	InflateRect, ebx, -32, -32		; Adjust for border
 			mov	eax, [ebx + 8]
 			sub	eax, [ebx]
 			  			
			invoke	CreateWindowEx,
				WS_EX_STATICEDGE or WS_EX_CLIENTEDGE,  ADDR EditClass, NULL, \
				WS_VISIBLE or WS_BORDER or WS_CHILD or ES_AUTOHSCROLL, \
				32, 32, eax, 22, edi, 64, Wc.hInstance, NULL
			
			mov	EditWnd, eax
			mov	edi, eax				; Save copy of window
			and	eax, eax
			jnz	@f
			
			int	3
			
		@@:	invoke	SetWindowLong, eax, GWL_WNDPROC, ADDR EditProc
			mov	EditMap, eax
			
			invoke	GetStockObject, ANSI_VAR_FONT
			invoke	SendMessage, edi, WM_SETFONT, eax, 0
			invoke	SendMessage, edi, EM_SETMARGINS, EC_LEFTMARGIN or EC_RIGHTMARGIN, 0180018H
			invoke	SetFocus, edi
			
   			stc					; Inhibit default processing by setting carry
  			ret
ScanMap	proc

	; EBX = Pointer to default procedure if window has been sub/super classed.
	; ECX = Number of handlers in map (can be NULL, although not usual).

		lodsd				; Get address of default window procedure.
		mov	ebx, eax		; Save in case window has been sub/super classed
		lodsw				; Number of messages in map
		cwde				; Sign extend to 32 bits, can be NULL but not usually the case
		
		.if	eax				; Does map have defninitions
			mov	ecx, eax		; Move into counter register
		
		; Cycle through each of the application defined handlers until a match is found,
		; otherwise just automatically drop into default message processing.

		@@:	lodsw				; Get message number from map
			mov	edx, eax		; Copy it so next instruction doesn't destroy
			lodsd				; Pointer to application defined handler
			
			.if	dx == [esp + 8]		; When match if found, execute handler
				lea	esi, [esp + 4]		; ESI points to API parameters
				call	eax			; Call handler
				jnc	@F			; Do default processing if no carry
		
				xor	eax, eax		; Set default return condition
				ret	16			; Clean-up stack and return.
			.endif

			loopnz	@B			; Keep comparing until ECX = Zero.
		.endif
	
	@@:	; Either there wasn't a need to handle this event or the handler requires default processing
	
		.if 	!ebx
			jmp	DefWindowProc	; Do default processing for this window
		.endif
	
	; Processing of a subclassed window requires address of default code so it will
	; be placed on stack at this point.
		
		xchg	[esp], ebx		; Exchange default processing address
		push	ebx			; with return and push on stack again
		jmp	CallWindowProc	; Do default processing for this window

Dani AI

Generated

Nice, compact dispatcher — good choice for small Win32 apps. A few clarifications and safety suggestions to make the approach more robust and easier to maintain over time.

The dispatcher hands each handler a pointer to the API parameters rather than relying on normal stack offsets. Concretely, the handler should treat the pointer as a parameter block: HWND at offset 0, UINT at +4, WPARAM at +8, LPARAM at +12. Handlers therefore must read arguments via that pointer (or accept a pointer parameter) rather than assuming the usual [esp+4]/[esp+8] stack offsets, because the dispatcher calls the handler in a way that changes stack-return-address layout. Also ensure the handler places the correct result in EAX before signaling “handled” — some messages require specific return values.

The current design uses a CPU flag to indicate “handled vs default” (handler sets the flag to suppress default processing). That is compact but a little fragile: a handler must deliberately set/clear the carry bit before returning. An alternative is to have handlers return a boolean in EAX (nonzero = handled) and let the dispatcher test EAX; that is clearer and survives compiler/ABI variations better.

Small hardening tips: zero-extend the 16-bit map count into a 32-bit register (for example with a movzx) rather than sign-extending, so very large maps are not misinterpreted as negative. Avoid depending on leftover flags across calls (use explicit loop/decrement or test logic rather than loopnz unless you carefully control which flags are live). Finally, note this is 32-bit/x86-specific: porting to x64 requires changing the calling convention and using GetWindowLongPtr/SetWindowLongPtr or the newer subclassing helpers (DefSubclassProc/SetWindowSubclass) rather than relying on stack offsets.

Acknowledgement: the original pattern and example by is a solid base; these changes improve safety and portability without changing the dispatch model.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.