�Eclipse Corner Article |
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
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.
��� /* 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.
��� 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:
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.
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.