3

I did some Win32 GUI programming several years ago. Now I am using Java Swing.

Just out of curiosity, where is the Swing counterpart of Win32 message loop logic? In Win32, it is achieved with the API GetMessage(). I guess it must have been wrapped deeply somewhere.

smwikipedia
  • 52,824
  • 76
  • 267
  • 432

1 Answers1

12

Overview

The following diagram broadly illustrates how Swing/AWT works on the Windows platform:

       Our Listeners
             ▲
             │ (Events dispatched to our code by EDT)
 ╭ ◀─────────┴───────────╮
 │ Event Dispatch Thread │
 ╰───────────▲─────────▶ ╯
             │ (Events pulled from the queue by EDT)
             │
        Event Queue
             ▲
             │ (Events posted to the queue by WToolkit)
 ╭ ◀─────────┴───────────╮
 │    WToolkit Thread    │
 ╰───────────▲─────────▶ ╯
             │ (Messages pulled by WToolkit via PeekMessage)
             │
        Windows API

This architecture is almost entirely hidden from us by the event-driven abstraction. We only interact with the top-most end when events are triggered (actionPerformed, paintComponent, etc.) and by occasionally posting events ourselves (invokeLater, repaint, etc.).

Official documentation on the subject tends to be very general so I'm going to use (very paraphrased) excerpts from the source code.

Event Dispatch Thread

The EDT is the Swing event processing thread and all Swing programs run primarily on this thread. For the most part, this is just the AWT system and it's located in java.awt.EventDispatchThread.

The event dispatching system is pretty dispersed, so I'll walk through a specific example supposing a JButton has been clicked.

To begin figuring out what's going on, we might look at a stack trace.

class ClickStack {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                JButton button = new JButton("Click for stack trace");

                button.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent ae) {
                        new Error().printStackTrace(System.out);
                    }
                });

                frame.add(button);
                frame.pack();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setVisible(true);
            }
        });
    }
}

This program gets us a call stack like the following:

at sscce.ClickStack$1$1.actionPerformed
at javax.swing.AbstractButton.fireActionPerformed
...
at javax.swing.DefaultButtonModel.setPressed
at javax.swing.plaf.basic.BasicButtonListener.mouseReleased
at java.awt.Component.processMouseEvent
...
at java.awt.Component.processEvent
...
at java.awt.Component.dispatchEventImpl
...
at java.awt.Component.dispatchEvent
at java.awt.EventQueue.dispatchEventImpl
...
at java.awt.EventQueue.dispatchEvent
at java.awt.EventDispatchThread.pumpOneEventForFilters
at java.awt.EventDispatchThread.pumpEventsForFilter
...
at java.awt.EventDispatchThread.pumpEvents
at java.awt.EventDispatchThread.run

If we take a look at the EventDispatchThread.run method, we see:

public void run() {
    try {
        pumpEvents(...);
    } finally {
        ...
    }
}

EventDispatchThread.pumpEvents takes us to EventDispatchThread.pumpEventsForFilter which contains the outer loop logic:

void pumpEventsForFilter(...) {
    ...
    while(doDispatch && ...) {
        pumpOneEventForFilters(...);
    }
    ...
}

An event is then pulled off the queue and sent off for dispatch in EventDispatchThread.pumpOneEventForFilters:

void pumpOneEventForFilters(...) {
    AWTEvent event = null;
    ...
    try {
        ...
        EventQueue eq = getEventQueue();
        ...
        event = eq.getNextEvent();
        ...
        eq.dispatchEvent(event);
        ...
    } catch(...) {
        ...
    } ...
}

java.awt.EventQueue contains logic where the type of event is narrowed and the event is further dispatched. EventQueue.dispatchEvent calls EventQueue.dispatchEventImpl where we see the following decision structure:

if (event instanceof ActiveEvent) {
    ...
    ((ActiveEvent)event).dispatch();
} else if (src instanceof Component) {
    ((Component)src).dispatchEvent(event);
    ...
} else if (src instanceof MenuComponent) {
    ((MenuComponent)src).dispatchEvent(event);
} else if (src instanceof TrayIcon) {
    ((TrayIcon)src).dispatchEvent(event);
} else if (src instanceof AWTAutoShutdown) {
    ...
    dispatchThread.stopDispatching();
} else {
    ...
}

Most events we are familiar with go through the Component path.

Component.dispatchEvent calls Component.dispatchEventImpl which, for most listener-type events, calls Component.processEvent where the event is narrowed down and forwarded again:

/**
 * Processes events occurring on this component. By default this
 * method calls the appropriate process<event type>Event
 * method for the given class of event.
 * ...
 */
protected void processEvent(AWTEvent e) {
    if (e instanceof FocusEvent) {
        processFocusEvent((FocusEvent)e);
    } else if (e instanceof MouseEvent) {
        switch(e.getID()) {
          case MouseEvent.MOUSE_PRESSED:
          case MouseEvent.MOUSE_RELEASED:
          case MouseEvent.MOUSE_CLICKED:
          case MouseEvent.MOUSE_ENTERED:
          case MouseEvent.MOUSE_EXITED:
              processMouseEvent((MouseEvent)e);
              break;
          case ...:
              ...
        }
    } else if (e instanceof KeyEvent) {
        processKeyEvent((KeyEvent)e);
    } else if (e instanceof ComponentEvent) {
        processComponentEvent((ComponentEvent)e);
    } else if (...) {
        ...
    } ...
}

For a JButton click, we're following a MouseEvent.

These low level events ultimately have a single handler internal to the Component. So for example, we might have a look at javax.swing.plaf.BasicButtonListener which implements a number of listener interfaces.

BasicButtonListener uses the mouse events to change the pressed state of the button model. Finally, the button model determines if it's been clicked in DefaultButtonModel.setPressed, fires an ActionEvent and our listener's actionPerformed gets called.

Native Messaging

How the actual native window is implemented is of course platform-specific but I can go through the Windows platform a bit since it's what you asked about. You'll find the Windows platform stuff in the following directories:

The Windows implementation of java.awt.Toolkit, which is sun.awt.windows.WToolkit, starts a separate thread for the actual message loop. WToolkit.run calls a JNI method eventLoop. A comment in the source file explains that:

/*
 * eventLoop() begins the native message pump which retrieves and processes
 * native events.
 * ...

This leads us to the C++ AwtToolkit class, located in awt_Toolkit.h and awt_Toolkit.cpp (other classes follow the same file name convention).

The native implementation of eventLoop calls AwtToolkit::MessageLoop:

AwtToolkit::GetInstance().MessageLoop(AwtToolkit::PrimaryIdleFunc,
                                      AwtToolkit::CommonPeekMessageFunc);

(AwtToolkit::CommonPeekMessageFunc calls PeekMessage, which is the non-blocking alter-ego of GetMessage.)

This is where the outer loop is located:

UINT
AwtToolkit::MessageLoop(IDLEPROC lpIdleFunc,
                        PEEKMESSAGEPROC lpPeekMessageFunc)
{
    ...

    m_messageLoopResult = 0;
    while (!m_breakMessageLoop) {

        (*lpIdleFunc)();

        PumpWaitingMessages(lpPeekMessageFunc); /* pumps waiting messages */
        ...
    }
    ...
}

AwtToolkit::PumpWaitingMessages actually has a familiar-looking message loop, which calls TranslateMessage and DispatchMessage:

/*
 * Called by the message loop to pump the message queue when there are
 * messages waiting. Can also be called anywhere to pump messages.
 */
BOOL AwtToolkit::PumpWaitingMessages(PEEKMESSAGEPROC lpPeekMessageFunc)
{
    MSG  msg;
    BOOL foundOne = FALSE;
    ...

    while (!m_breakMessageLoop && (*lpPeekMessageFunc)(msg)) {
        foundOne = TRUE;
        ProcessMsg(msg); // calls TranslateMessage & DispatchMessage (below)
    }
    return foundOne;
}

void AwtToolkit::ProcessMsg(MSG& msg)
{
    if (msg.message == WM_QUIT) {
        ...
    }
    else if (msg.message != WM_NULL) {
        ...

        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);
    }
}

(And recall that DispatchMessage calls the WindowProc callback.)

The native window is wrapped by a C++ object which has platform-specific stuff, as well as a loose parallel of some of the API we have in Java code.

There seem to be a couple of WindowProc functions. One is just used internally by the toolkit, AwtToolkit::WndProc, along with an empty window.

The WindowProc function we're actually interested in is AwtComponent::WndProc. WndProc calls a virtual function called AwtComponent::WindowProc. Some subclasses override WindowProc (e.g. AwtFrame::WindowProc), but a majority of messages are handled by AwtComponent::WindowProc. For example, it contains the following switch case:

case WM_LBUTTONDOWN:
case WM_LBUTTONDBLCLK:
    mr = WmMouseDown(static_cast<UINT>(wParam), myPos.x, myPos.y,
                     LEFT_BUTTON);
    break;

AwtComponent::WmMouseDown begins a series of calls that posts a java.awt.MouseEvent to the EventQueue in Java:

SendMouseEvent(java_awt_event_MouseEvent_MOUSE_PRESSED, now, x, y,
               GetJavaModifiers(), clickCount, JNI_FALSE,
               GetButton(button), &msg);

After the event is posted, we are ultimately led back to the top where the event is seen on the EDT.

Radiodef
  • 35,285
  • 14
  • 78
  • 114
  • @smwikipedia No problem in both regards. ; ) – Radiodef Apr 07 '15 at 02:31
  • @Radiodef Does the message pumping loop in AwtToolkit::PumpWaitingMessages keeps running when java VM is started and running? – chunsj Dec 26 '15 at 01:13
  • @chunsj It's been awhile since I've looked at this, but I think the event queue starts up whenever the first Swing/AWT call is made (like `invokeLater` or other calls) and ends whenever the VM shuts down. If you never use Swing/AWT, the event queue never starts. – Radiodef Dec 26 '15 at 03:39
  • @Radiodef Thank you, this helps me solve my own weird problem in my JNI module greatly. You're my hero :-) – chunsj Dec 26 '15 at 13:44