A Little More on Messages and an Introduction to the Mouse

Messages are basic to Windows GUI programming. Almost everything a GUI program does is triggered by messages. Writing a program that repeatedly waits for messages, results in an event-driven style of programming, where each message represents an event.
    The messages WM_QUIT and WM_DESTROY were introduced so that an application can terminate gracefully. In this section, we introduce mouse messages that show how a program can respond to mouse events in a simple way. The mouse messages are associated with the framed area of a window, the client area.
    For MS-DOS programmers, writing a window procedure is somewhat like writing an INT 21H interceptor, with the message number on the stack instead of in AH. And instead of jumping to the old DOS address for standard behavior, you call (or jump to) DefWindowProc.
    Windows produces a lot of messages, but the typical application will provide a custom response to only a small percentage of them. Most of the messages can be classified as notifications--"this happened" or "this is about to happen" messages. A few messages can be interpreted as commands. [ Back to Win32 ASM Page ]

Message systems

Message systems can be viewed as a collection of senders (or producers) and receivers (or consumers) of messages. The messages are typically managed by holding them in queues. In this manner, messages are sent and received by storing and fetching them to and from the queue. This allows senders to signal the receivers at a convenient time, and it allows the receivers to respond at a convenient time.

Our basic GUI program handles the reception of messages in two parts: the first part inspects the message queue, and the second part actually decides how to respond to a given message. Our basic GUI program is written primarily as a receiver or consumer of messages. The senders or producers of these messages are either the Windows API functions or the Windows system itself.

Messages

A message is a packet of information that contains an identifying number. Numbers not reserved by Windows can be used for our own purposes. Some numbers have been given names by Windows, and we have used these names -- those starting with WM.
    Messages are delivered to a window by invoking the window's assigned window procedure. As described before, window procedures are assigned in two steps. First, by registering a window class containing the address of a window procedure; and, second, by creating a window using that class.
    Most, but not all, messages are delivered to windows. WM_QUIT is an example of a message that never goes to a window. That's why we don't handle it in a window procedure.
    The code that is written to respond to a message is often called the message handler for that message. Different windows can respond differently to the same message identifier. (The message "packet" itself is directed to only one window.)

Allocation of message identifiers

    The message identifying numbers, or message identifiers, are allocated as follows:

The client area

The first mouse messages we will look at are usually delivered only when the cursor (which represents the mouse) is pointing to a spot in the client area.
    The client area is the display area framed by the window border. If they exist, the title, menu, and scroll bars are excluded from the client area.

Mouse movement and buttons

A two-button mouse can produce the following fundamental messages whenever the cursor points to a location in the client area:

The example program

Our example program, winclick.asm, reports on mouse activity within our window.

Prolog and class registration

Here we create the basic window class.
       .386
        .model  flat

        ; If using TLINK32, don't include vclib.inc
        include vclib.inc    ; Microsoft VC++ .lib link names

        include win32hst.inc ; constants, structures, and entry names

        .data
        align   4
wcx     dd      size WNDCLASSEX           ; cbSize
        dd      CS_VREDRAW or CS_HREDRAW  ; style
        dd      WndProc                   ; lpfnWndProc
        dd      0,0                       ; cbClsExtra, cbWndExtra
        dd      0                         ; hInstance
        dd      0                         ; hIcon
        dd      0                         ; hCursor
        dd      COLOR_WINDOW+1            ; hbrBackground
        dd      0                         ; lpszMenuName
        dd      wndclsname                ; lpszClassName
        dd      0                         ; hIconSm

wndclsname db 'winmain',0

        .code

        public _start
        extrn   GetModuleHandle:near
        extrn   LoadIcon:near,LoadCursor:near
        extrn   RegisterClassEx:near

_start:
        push    large 0         ; NULL string pointer means
        call    GetModuleHandle ; get HINSTANCE/HMODULE of EXE file
        mov     [wcx.wcx_hInstance],eax

        push    large IDI_WINLOGO
        push    large 0         ; hInstance, 0 = stock icon
        call    LoadIcon
        mov     [wcx.wcx_hIcon],eax

        push    large IDC_ARROW
        push    large 0         ; hInstance, 0 = stock cursor
        call    LoadCursor
        mov     [wcx.wcx_hCursor],eax

        push    offset wcx
        call    RegisterClassEx

Window creation and message loop

We create our window and set it up to display our startup title. The window width is made long enough to avoid clipping of the title text.
       .data
        align   4

cwargs   dd   0                  ; dwExStyle
         dd   wndclsname         ; lpszClass
         dd   wnd_title          ; lpszName
         dd   WS_VISIBLE or WS_OVERLAPPED or WS_SYSMENU or WS_THICKFRAME \
              or WS_MINIMIZEBOX or WS_MAXIMIZEBOX  ; style

         dd   100                ; x
         dd   100                ; y
         dd   400                ; cx (width)
         dd   200                ; cy (height)
         dd   0                  ; hwndParent
         dd   0                  ; hMenu
         dd   0                  ; hInstance
         dd   0                  ; lpCreateParams

msgbuf MSG      <>

wnd_title db 'Mouse activity',0

        .code

        extrn   CreateWindowEx:near
        extrn   GetMessage:near,DispatchMessage:near
        extrn   ExitProcess:near

        sub     esp,48            ; allocate argument list
        mov     esi,offset cwargs ; set block move source
        mov     edi,esp           ; set block move destination
        mov     ecx,12            ; number of arguments
        rep movsd
        mov     eax,[wcx.wcx_hInstance]
        mov     [esp+40],eax      ; set hInstance argument in stack
        call    CreateWindowEx

msg_loop:
        push    large 0         ; uMsgFilterMax
        push    large 0         ; uMsgFilterMin
        push    large 0         ; hWnd (filter), 0 = all windows
        push    offset msgbuf   ; lpMsg
        call    GetMessage      ; returns FALSE if WM_QUIT
        or      eax,eax
        jz      end_loop

        push    offset msgbuf
        call    DispatchMessage

        jmp     msg_loop

end_loop:
        push    large 0 ; (error) return code
        call    ExitProcess

Message dispatch

The following is one way to dispatch messages. It's not bad because there are so few messages. A bigger set of messages would be easier to handle with an address table.
   .code

        extrn   DefWindowProc:near,PostQuitMessage:near

WndProc:
        mov     eax,[esp+4+4]           ; message ID
        cmp     eax,WM_DESTROY          ; about to start window destruction
        je      on_destroy
        cmp     eax,WM_LBUTTONDOWN      ; left mouse button pressed
        je      on_left_mouse_down
        cmp     eax,WM_LBUTTONUP        ; left mouse button released
        je      on_left_mouse_up
        cmp     eax,WM_RBUTTONDOWN      ; right mouse button pressed
        je      on_right_mouse_down
        cmp     eax,WM_RBUTTONUP        ; right mouse button released
        je      on_right_mouse_up
        cmp     eax,WM_MOUSEMOVE        ; mouse moved
        je      on_mouse_move
        jmp     DefWindowProc           ; delegate other message processing

on_destroy:
        push    large 0
        call    PostQuitMessage

        xor     eax,eax
        ret     16

Mouse message handlers

This is where we respond to the mouse messages. The text in the title bar is changed by calling SetWindowText. The text displayed depends on the mouse activity when the cursor is pointing to within the client area. The code is left unoptimized so that it shows more clearly where the hwnd parameter is coming from. (The +4 skips the stacked EIP, and +0 is the offset of the first argument.)
       .data
        align   4
text_ldown db 'Left mouse button down',0
text_lup   db 'Left mouse button up',0
text_rdown db 'Right mouse button down',0
text_rup   db 'Right mouse button up',0
text_mmove db 'Mouse moved',0

        .code

        extrn   SetWindowText:near

on_left_mouse_down:
        mov     eax,[esp+4+0]   ; get hwnd before changing ESP
        push    offset text_ldown
        push    eax
        call    SetWindowText

        xor     eax,eax
        ret     16

on_left_mouse_up:
        mov     eax,[esp+4+0]   ; get hwnd before changing ESP
        push    offset text_lup
        push    eax
        call    SetWindowText

        xor     eax,eax
        ret     16

on_right_mouse_down:
        mov     eax,[esp+4+0]   ; get hwnd before changing ESP
        push    offset text_rdown
        push    eax
        call    SetWindowText

        xor     eax,eax

        ret     16

on_right_mouse_up:
        mov     eax,[esp+4+0]   ; get hwnd before changing ESP
        push    offset text_rup
        push    eax
        call    SetWindowText

        xor     eax,eax
        ret     16

on_mouse_move:
        mov     eax,[esp+4+0]   ; get hwnd before changing ESP
        push    offset text_mmove
        push    eax
        call    SetWindowText

        xor     eax,eax
        ret     16

        end     _start