About 3 years ago, I’ve released a MASM version of message cracker for handling windows messages here: http://devpinoy.org/forums/p/516/1524.aspx#1524
I really find this include file useful whenever I am working on a GUI based win32 program using MASM. Unfortunately, only few people know about it, so I hope this blog post will provide a late introduction about what this include file is all about.
About message cracker:
To make it simple, message cracker is a set of macros that you can line-up to your message-handling routine to enable window-message handling to a separate (or inline) procedure. This line-up of macro calls may improve the readability of your program compared to a long list of .IF/.ELSEIF/.ENDIF.
To see the advantage of using a message-cracker, let’s see how a typical message-handling routine would look like:
WinProc proc hWnd:HWND, uMsg:dword, wParam:WPARAM, lParam:LPARAM
LOCAL ps:PAINTSTRUCT
.if uMsg == WM_INITDIALOG
invoke LoadIcon, hInstance, ICON_APP
invoke SendMessage, hWnd, WM_SETICON, ICON_BIG or ICON_SMALL, eax
.elseif uMsg == WM_COMMAND
mov eax, wParam
.if eax == ID_BUTTON1
; ID_BUTTON1 was accessed, handle the event here
.elseif eax == ID_BUTTON2
; ID_BUTTON2 was accessed, handle the event here
.endif
.elseif uMsg == WM_PAINT
invoke BeginPaint, hWnd, addr ps
; Peform paint routine here
nvoke EndPaint, hWnd, addr ps
.elseif uMsg == WM_CLOSE
invoke EndDialog, hWnd, 0
.endif
xor eax,eax
ret
WinProc endp
And that’s kind of clean and readable, because the procedure handles only 4 window-messages. However, you can imagine how this procedure will look like if it handles 20 or more window-messages. It will become a long procedure that is hard to trace and not too readable.
And as you have already known it, there’s a neat solution for that kind of situation. This is, to break the handler for each window-message to their own respective handler-procedure. If I break-up a the example above, it will be done similar to:
WinProc proc hWnd:HWND, uMsg:dword, wParam:WPARAM, lParam:LPARAM
.if uMsg == WM_INITDIALOG
push hWnd
call WmInitDialog
.elseif uMsg == WM_COMMAND
push hWnd
push wParam
call WmCommand
.elseif uMsg == WM_PAINT
push hWnd
call WmPaint
.elseif uMsg == WM_CLOSE
invoke EndDialog, hWnd, 0
.endif
xor eax,eax
ret
WinProc endp
WmInitDialog proc hWnd:HWND
invoke LoadIcon, hInstance, ICON_APP
invoke SendMessage, hWnd, WM_SETICON, ICON_BIG or ICON_SMALL, eax
ret
WmInitDialog endp
WmCommand proc hWnd:HWND, wParam:WPARAM
mov eax, wParam
.if eax == ID_BUTTON
; ID_BUTTON was accessed, handle the event here
.elseif eax == ID_BUTTON2
; ID_BUTTON2 was accessed, handle the event here
.endif
ret
WmCommand endp
WmPaint proc hWnd:HWND
LOCAL ps:PAINTSTRUCT
invoke BeginPaint, hWnd, addr ps
; Peform paint routine here
invoke EndPaint, hWnd, addr ps
ret
WmPaint endp
Then, the more window-messages you add, the more handler-procedure you will need to add as well. Or you may leave it inline if the handler contains only 1 line, similar to what I’ve done to WM_CLOSE above.
The idea of a message-cracker is just that -> Break the handler for each window-message to their own respective handler-procedure. In addition, the message-cracker helps you get the needed information for quickly handing a particular message.
For example, the WM_MOUSEMOVE message passes the x and y coordinates of mouse-pointer into LPARAM. You need to extract the high-word (y) and low-word (x) from LPARAM to actually use the x and y coordinate.
When assigning a handler-procedure to handle a window-message, message-cracker will actually do much of the work for these small details. Your handler-procedure will receive only the needed information (argument) to work with the window-message itself.
Here’s how the handler of WM_MOUSEMOVE looks like:
WmMouseMove proc hWnd:HWND, x:DWORD, y:DWORD
; add code to handle mouse move
ret
WmMouseMove endp
There’s no more LPARAM to worry about. Your handler-procedure will receive x and y coordinates, as it should.
Using it:
First you need to get the message-cracker include file. Download the attachment, and extract the file MsgCrack.inc.
In your program, include the MsgCrack.inc
include MsgCrack.inc
Then you’re ready message cracking.
How to add handler
Inside your window procedure, start cracking messages with this block:
.handle_msg_begin
.handle_msg_end
The .handle_msg_begin accepts 4 optional arguments:
.handle_msg_begin [HWND], [MSG], [WPARAM], [LPARAM], [REG]
The default is:
HWND = hWnd
MSG = uMsg
WPARAM = lParam
LPARAM = wParam
REG = EAX
If your window procedure is defined this way:
WndProc proc hWnd:HWND, uMsg:dword, wParam:WPARAM, lParam:LPARAM
xor eax, eax
ret
WndProc endp
Where all the arguments matches the default arguments, then you don’t need to specify anything, just add the block as is:
WndProc proc hWnd:HWND, uMsg:dword, wParam:WPARAM, lParam:LPARAM
.handle_msg_begin
.handle_msg_end
xor eax, eax
ret
WndProc endp
However, if your window procedure is defined differently, something like this one:
WndProc proc hWin:DWORD, uMsg:DWORD, aParam:DWORD, bParam:DWORD
xor eax, eax
ret
WndProc endp
Then you have to tell the message-cracker which identifier should be used:
WndProc proc hWin:DWORD, uMsg:DWORD, aParam:DWORD, bParam:DWORD
.handle_msg_begin hWin, uMsg, aParam, bParam, eax
.handle_msg_end
xor eax, eax
ret
WndProc endp
That’s the quick part of it. Check the comment I’ve written inside MsgCrack.inc for detail about these default identifier.
Now, let’s move to the next part. Let’s register a handler-procedure with window-message. This is done using the macro .handle_msg with the following arguments:
.handle_msg <window message constant>, <handler function>
Where <window message constant> is the constant defined in windows.inc include file (MASM32), WM_PAINT, WM_MOUSEMOVE and WM_CLOSE, are some of the window-message constant. The macro won’t work if you used integer value, so you should always use the defined constant. Then the <handler function> is the name of your handler-procedure.
Here’s the similar code above, redefined to use the message cracker:
WinProc proc hWnd:HWND, uMsg:dword, wParam:WPARAM, lParam:LPARAM
.handle_msg_begin
.handle_msg WM_INITDIALOG, WmInitDialog
.handle_msg WM_COMMAND, WmCommand
.handle_msg WM_PAINT, WmPaint
.handle_msg_inline WM_CLOSE
invoke EndDialog, hWnd, 0
.handle_msg_end
xor eax,eax
ret
WinProc endp
WmInitDialog proc hWnd:HWND, defCtrl:HWND, initData:dword
invoke LoadIcon, hInstance, ICON_APP
invoke SendMessage, hWnd, WM_SETICON, ICON_BIG or ICON_SMALL, eax
ret
WmInitDialog endp
WmCommand proc hWnd:HWND, id:dword, hWndCtl:HWND, codeNotify:dword
.if id == ID_BUTTON
; ID_BUTTON was accessed, handle the event here
.elseif id == ID_BUTTON2
; ID_BUTTON2 was accessed, handle the event here
.endif
ret
WmCommand endp
WmPaint proc hWnd:HWND
LOCAL ps:PAINTSTRUCT
invoke BeginPaint, hWnd, addr ps
; Peform paint routine here
invoke EndPaint, hWnd, addr ps
ret
WmPaint endp
The noticeable difference is the replacement of .if/.elseif/.endif statements into .handle_msg_begin/.handle_msg/.handle_msg_inline/.handle_msg_end. And in the handler-procedures, the arguments have changed to reflect the information passed by the window-message (see the argument list of WmCommand handler).
Now, if you notice WM_CLOSE message handling is different, because we don’t want to branch the handler to a separate handler-procedure, so we use .handle_msg_inline to indicate that WM_CLOSE handling follows on the next line (inline):
.handle_msg_inline WM_CLOSE
Another good thing about message-cracker is, you can register similar window-messages to a single handler-procedure, if they share similar argument listing. For example, assigning 3 button-up messages, WM_LBUTTONUP, WM_MBUTTONUP, WM_RBUTTONUP to WmButtonUp should be as easy as this:
.handle_msg WM_LBUTTONUP, WmButtonUp
.handle_msg WM_MBUTTONUP, WmButtonUp
.handle_msg WM_RBUTTONUP, WmButtonUp
From this point, I’ve probably covered the how-to-use part of the include file., now let me tell you how you can create your handler-procedure.
Creating Handler Procedure:
Open MsgCrack.inc file, then search the window-message you want to handle e.g., WM_SETCURSOR. You can see the defined macro for handling that window-message as:
; WM_handler(hWnd:HWND, hWndCursor:HWND, codeHitTest:dword, msg:dword)
.handle_WM_SETCURSOR macro in_hWnd, in_lParam, in_wParam, addr_handler
The part we are interested is the comment line above it, as:
WM_handler(argument-listing)
That’s your cue about the argument listing, inside the parentheses. Copy-it, then paste it in a new procedure, similar to this one:
WmSetCursor proc hWnd:HWND, hWndCursor:HWND, codeHitTest:dword, msg:dword
ret
WmSetCursor endp
That’s it. For any questions, suggestions, bug-report, or any feedback, please post it below.
See the attachment for the sample program and the include file.
Happy coding.
-chris