[REDUX] Message Cracker for MASM

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

 

Attachment: MsgCrack.zip
Published 10-25-2008 11:36 PM by cvega
Filed under:

Comments

# re: [REDUX] Message Cracker for MASM

Sunday, October 26, 2008 7:25 PM by modchip

Indeed, we'll have a great time coding using this. I've wanted to try this out long ago -- but forgot... *blush* Thanks for re-releasing this! :D