Copyright © 2001 Object Technology International, Inc.  
 Eclipse Corner Article

SWT: The Standard Widget Toolkit

PART 1: Implementation Strategy for Java™ Natives

The first in a series of articles about the design ideas behind SWT.
Summary
SWT is the software component that delivers native widget functionality for the Eclipse platform in an operating system independent manner.  It is analogous to AWT/Swing in Java with a difference - SWT uses a rich set of native widgets.  Even in an ideal situation, industrial strength cross platform widget libraries are very difficult to write and maintain.  This is due to the inherent complexity of widget systems and the many subtle differences between platforms.  There are several basic approaches that have helped significantly to reduce the complexity of the problem and deliver high quality libraries.  This article discusses one of them, the low level implementation techniques used to implement SWT on different platforms.  Examples are drawn from the Windows® and Motif implementations.

By Steve Northover, OTI
March 22, 2001


Portable and Native - It Can't Be Done!

Developers demand portable graphics and widgets to allow them to build user interfaces that are competitive with shrink-wrapped applications built using platform specific tools.  They need access to platform specific features, with well defined API boundaries. SWT delivers this functionality using a small and consistent API.  This API is implemented on different platforms using a combination of Java code and JNI natives specific to each platform.

SWT is implemented entirely in one language: Java.  How can this be true when SWT uses native widgets that provide an API in C?  The answer is that Java provides a native interface to C (JNI) that is used by SWT to invoke the operating system from Java code.  JNI is the standard mechanism used by all Java programs to invoke code written in C. SWT goes one step further by enforcing a one-to-one mapping between Java native methods and operating system calls.  The fact that this mapping is strictly enforced is one of the most critical factors in the success of SWT.

A Tale of Two Implementations

Let's take a look at the implementation of SWT Text widget on two different platforms.  The Text widget provides the ability to set the selection.  SWT application code that uses this API might look something like this:

    /* Select positions 2 to 5 */
    text.setText ("0123456780");
    text.setSelection (2, 5);

The method signature for setSelection in class Text looks like this:

    /**
    * Sets the selection.
    * <p>
    * Indexing is zero based.  The range of a selection is from 0..N
    * where N is the number of characters in the widget.
    */
    public void setSelection (int start, int end)

Here is the Windows implementation of setSelection:

    public void setSelection (int start, int end) {
        OS.SendMessage (handle, OS.EM_SETSEL, start, end);
    }

What are SendMessage and EM_SETSEL?  Windows programmers recognize this right away.  It's SendMessage, the mechanism that is used to talk to Windows controls. EM_SETSEL is the message that tells the text control to set the selection.  It's not easy reading, but it is familiar to a Windows programmer.  The rest of us get to read the Microsoft® Developer Network (MSDN) Library!

Here is the Java code for the SWT class OS on Windows:

    class OS {
        public static final int EM_SETSEL = 0xB1;
        public static final native int SendMessage (int hWnd, int Msg, int wParam, int lParam);
        ...
    }

How is the SendMessage native implemented?  Here is the C code on Windows:

    JNIEXPORT jint JNICALL Java_org_eclipse_swt_internal_win32_OS_SendMessage__IIII
      (JNIEnv *env, jclass that, jint hWnd, jint Msg, jint wParam, jint lParam)
    {
        return (jint) SendMessage((HWND)hWnd, Msg, wParam, lParam);
    }

Notice that the only thing this native does is pass the Java call straight on through to the Windows API. But what about other operating systems?  Does the fact that setSelection is implemented in terms of the Windows API mean that SWT is not portable?  While it is true that the Windows implementation of Text is not portable, application code that uses Text is.  How is this achieved?  SWT provides a different Text class for each platform, but the signature of every public method is the same.  Java code that calls SWT does not know or care which Text class is referenced at run time. SendMessage is not SWT API.

Here is the implementation of setSelection on Motif (Java and JNI C):

    public void setSelection (int start, int end) {
        int xDisplay = OS.XtDisplay (handle);
        if (xDisplay == 0) return;
        OS.XmTextSetSelection (handle, start, end, OS.XtLastTimestampProcessed (xDisplay));
        OS.XmTextSetInsertionPosition (handle, end);
    }

    class OS {
        public static final native void XmTextSetSelection (int widget, int first, int last, int time);
        public static final native int XtLastTimestampProcessed (int display);
        public static final native void XmTextSetInsertionPosition (int widget, int position);
        public static final native int XtDisplay (int widget);
        ...
    }

    JNIEXPORT void JNICALL Java_org_eclipse_swt_internal_motif_OS_XmTextSetSelection
      (JNIEnv *env, jclass that, jint widget, jint first, jint last, jint time)
    {
        XmTextSetSelection((Widget)widget, first, last, time);
    }

    ...

What are XtDisplay, XmTextSetSelection, XtLastTimestampProcessed and XmTextSetInsertionPosition?  They don't mean much to a Windows programmer, but they are familiar to anyone who has ever programmed Motif.  Now it's the Windows programmer's turn to consult the Motif man pages!

The example code above was taken directly from SWT but has been simplified by removing range and error checking code for the sake of the example.  However, the code that is doing the real work - setting the selection - is identical to that found in the product.

One to One Mapping - No Custom Natives

Take a moment to review the Java and C code for setSelection in the previous section.  Wouldn't it be easier to implement one Text class for all SWT platforms and hide the platform differences in the natives?  Such an implementation might look like this:

    public void setSelection (int start, int end) {
        nativeSetSelection (start, end)
    }
    static final native void nativeSetSelection (int start, int end);

    JNIEXPORT void JNICALL Java_org_eclipse_swt_widgets_text_nativeSetSelection
      (JNIEnv *env, jclass that, jobject widget, jint first, jint last)
    {
    #ifdef WINDOWS
        HWND hWnd = SWTGetHandleFromJavaWidget (widget);
        SendMessage(hWnd, Msg, wParam, lParam);
    #endif
    #ifdef MOTIF
        Widget *w = SWTGetHandleFromJavaWidget (widget);
        Display xDisplay = XtDisplay (w);
        if (xDisplay == NULL) return;
        XmTextSetSelection (w, start, end, XtLastTimestampProcessed (xDisplay));
        XmTextSetInsertionPosition (w, end);
    #endif
    }

Isn't this easier than having a different Text class on each platform?  The answer is a resounding "No".  Why?  In the case of the Text widget, the code to set the selection is pretty simple but even this causes problems.  Before we get into the discussion, consider this:

Calling the operating system directly from Java helps with debugging. The following problem occurred in an early version of SWT for Windows:  when the selection was set in the text widget, sometimes the widget did not scroll to show the i-beam.  Where was the problem?  The code that demonstrated the problem was complicated, but it was clear from stepping through the Java code and consulting the MSDN Library that the Java implementation of setSelection was correct.  In fact, because of the one-to-one mapping between our Java natives and C, it was possible to write a simple C example to help isolate the problem and submit a bug report to Microsoft.  Why was this so easy?  Because, as we have said before, nothing extra ever happens in an SWT native. The Java call is passed right on through to the operating system.  This means that C code is guaranteed to exhibit the same behavior.  This is great news for debugging and maintenance.

Performance problems are legendary in widget toolkits and finding them is a black art.  Where is the performance problem?  Is it in the Java code or the natives?  Fortunately, SWT natives can't be the problem. We are guaranteed that once we are in a native, the limiting factor is the speed of the operating system - something beyond our control.  This is great news for performance tuning: look at the Java code.  In fact, one quickly develops a sense of which operating system operations are expensive and which are cheap. Best of all, this knowledge is accurate.  A C program that makes the same sequence of operating system calls will exhibit the same performance characteristics.  This is a feature of the one-to-one mapping.

What happens when you try to debug a segment fault (or GP)?  It's easy enough to step into a Java method and examine arguments but not possible to step into a native.  Fortunately, nothing special happens in SWT natives so it's easy enough to isolate the code that is causing the problem.  While on the subject of GPs, wouldn't it make sense for SWT natives to check their parameters before making the operating system call?  It's tempting to check for NULL or -1 to avoid the crash.  On the surface, this seems to make sense - after all, who wants to GP?  The answer, of course, is that this would violate the one-to-one mapping and would mean that an equivalent C program would not crash in the same place.  That's bad news for debugging and isolating a problem.

For someone implementing and maintaining SWT, the one-to-one mapping is extremely valuable.  For example, a Windows programmer knows right away how setSelection works, just by looking at the Java code.  Everyone else needs to read the MSDN Library.  It's not light reading, but the information is there.  The same thing is true for a Motif programmer for SWT on Motif, and for the other supported operating systems.  In fact, it's clear exactly how existing features work and new features are to be implemented.  The critial point here is that the documentation for the operating system applies to all SWT natives because they are a one-to-one mapping.

Adding new native features to SWT is a straightforward and well defined process. For example, implementing drag and drop and integrating it with the widgets was not difficult, despite the fact that these are two independent services.  Why was this so easy?  Nothing is hidden in the C code.  All of the operating system resources needed to implement SWT are manifested as simple Java objects making it easy to understand how SWT works and to make changes.  This allows SWT to be customized to support new platform dependent widgets and operating system services as they become available.

One last point: JNI is rich and powerful.  It allows you to allocate Java objects in C, get and set Java fields, invoke new VMs and throw exceptions.  The operating system, on the other hand, is typically more primitive.  For example, most operating system calls that access memory require you to allocate the buffer and pass in the size.  Java arrays know their size, so why do we need to pass it in?  JNI allows us to allocate objects in C, so why not allocate buffers in the C code?  Wouldn't it be better to try and "fix" the operating system API to make it more Java friendly?  The answer again is "No".  Any deviation from the one-to-one rule means that our Java code no longer behaves the same as the equivalent C code.  For example, allocating objects in JNI could introduce a hidden performance problem for Java code inside a tight loop.  Also, it may make sense to allocate one large buffer and pass in a smaller size, or reuse a buffer.  It's tempting to use JNI features to attempt to "fix" the operating system API but this is a huge mistake.

Conclusion

All of the natives in SWT are implemented using this simple and consistent strategy.  There is no C code to hide the low level details of the operating system such as the event loop, callbacks or the thread model.  No code reaches back into Java from C to get a field or invoke a method.  Nothing is magic - everything is coded in Java using the terminology and documentation of the operating system.  Why is this such a big deal?  Some might claim that all SWT does is use JNI to invoke the operating system - nothing fancy. But that's the whole point.  Without a simple set of rules and a sense of restraint - a characteristic of SWT - it's just too easy for a widget toolkit to collapse under its own weight.
 

Java and all Java-based trademarks and logos are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States, other countries, or both.

Microsoft, Windows, Windows NT, and the Windows logo are trademarks of Microsoft Corporation in the United States, other countries, or both.