2 Messages and Message Loops
dxtr edited this page 2026-10-17 13:10:20 +02:00
This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Messages and Message Loops

Message loops

A thread with windows takes their messages from its queue and hands them to their window procedures. cl-win32 has two ways:

(win32:run-message-loop)   ; waits, until POST-QUIT-MESSAGE; => its code
(win32:process-messages)   ; hands over what is waiting, and returns

run-message-loop waits for messages and hands each over, until the thread calls post-quit-message; it returns the code given to post-quit-message. It wakes for messages sent from other threads too. Handing a message over allocates nothing, so the loop can run for the life of a program.

process-messages hands over the messages waiting and returns without waiting for more. It returns nil, or t and the code if it came across the quit message. Use it in a loop that does other work, such as a game loop that renders a frame after each round.

Both translate key messages: after a key press that types a character, Windows posts +wm-char+ with it, after dead keys. See Keyboard and Pointer.

An error in a window procedure is signalled from run-message-loop or process-messages, right after the message it happened in. The loop may be called again to go on:

(loop
  (handler-case (return (win32:run-message-loop))
    (error (condition)
      (log-error condition))))

Posting and sending

(win32:post-message window win32:+wm-close+)            ; returns at once
(win32:send-message window (+ win32:+wm-app+ 1) 7 0)    ; => the procedure's result

post-message puts a message on the queue of the window's thread and returns. It may be called from any thread.

send-message calls the window's procedure with the message and returns its result. From another thread than the window's, it waits until the window's thread takes the message, which it does while it waits for messages. So the window's thread must not be waiting for the sender, or neither goes on: don't hold a lock the window procedure needs while you send.

wparam is an unsigned integer and lparam a signed one, both 0 if left out.

Messages of your own

Numbers from +wm-app+ (#x8000) up are free for a program's own messages, and from +wm-user+ (#x400) up for a window class's. A message-only window with a message of your own is the usual way to give work to a thread with a message loop. See Recipes.

Looking ahead

(win32:peek-message)                         ; => the next message, its wparam, lparam and time
(win32:peek-message :window window)          ; only that window's
(win32:peek-message :first win32:+wm-keydown+ :last win32:+wm-keyup+)

peek-message returns the next message waiting for the calling thread, its wparam and lparam, and its time, without taking it; or nil if none is. :window and :first and :last narrow which messages count.

message-time is the time of the message being handled, in milliseconds since Windows started. Two messages Windows made for one action, such as the two key messages of AltGr, have the same time. message-position is where the pointer was on the screen then, which a message without a position of its own, such as +wm-capturechanged+, needs.

Taking parameters apart

Many messages pack two 16-bit numbers into one parameter.

Function Like Gives
low-word LOWORD bits 0–15, unsigned
high-word HIWORD bits 16–31, unsigned
signed-low-word GET_X_LPARAM bits 0–15, signed: pointer x
signed-high-word GET_Y_LPARAM, GET_WHEEL_DELTA_WPARAM bits 16–31, signed: pointer y, wheel turns
make-lparam MAKELPARAM two words put together
;; In a window procedure:
((= message win32:+wm-size+)
 (resize (win32:low-word lparam) (win32:high-word lparam))
 0)
((= message win32:+wm-mousemove+)
 (move-to (win32:signed-low-word lparam) (win32:signed-high-word lparam))
 0)

Pointer positions are signed: a drag that leaves the window, or a window on a display left of the main one, gives negative ones.

read-rect reads a RECT a parameter points to, as +wm-dpichanged+'s lparam does, and returns its left, top, right and bottom.

The messages

cl-win32 names the messages its users need most as constants:

  • Lifetime: +wm-nccreate+, +wm-create+, +wm-destroy+, +wm-ncdestroy+, +wm-close+, +wm-quit+.
  • Size and place: +wm-size+ with +size-restored+, +size-minimized+ and +size-maximized+; +wm-move+, +wm-windowposchanging+, +wm-windowposchanged+, +wm-getminmaxinfo+, +wm-entersizemove+, +wm-exitsizemove+, +wm-dpichanged+.
  • State: +wm-activate+, +wm-setfocus+, +wm-killfocus+, +wm-showwindow+, +wm-settext+, +wm-gettext+.
  • Painting: +wm-paint+, +wm-erasebkgnd+, +wm-setcursor+.
  • Keyboard: +wm-keydown+, +wm-keyup+, +wm-syskeydown+, +wm-syskeyup+, +wm-char+, +wm-syschar+, +wm-deadchar+, +wm-sysdeadchar+, +wm-syscommand+.
  • Pointer: +wm-mousemove+, +wm-mouseleave+, +wm-lbuttondown+, +wm-lbuttonup+, +wm-lbuttondblclk+, +wm-rbuttondown+, +wm-rbuttonup+, +wm-mbuttondown+, +wm-mbuttonup+, +wm-xbuttondown+ and +wm-xbuttonup+ with +xbutton1+ and +xbutton2+; +wm-mousewheel+ and +wm-mousehwheel+ with +wheel-delta+; +wm-capturechanged+.
  • Others: +wm-null+, +wm-user+, +wm-app+.

Each constant's docstring says what its parameters hold. Messages without a constant are numbers like any other: compare with the number from Windows' documentation.