Java Tip 86: Support native rendering in JDK 1.3

Gain portability and a quicker development cycle, without sacrificing performance of native GUI code

Before the arrival of JDK 1.3, you could only use JNI for non-UI work. JDK 1.3 introduces the new Java 2 AWT Native Interface (J2AWT) API that lets you use non-Java GUI components within Java programs, though in doing so you lose the portability of a pure Java solution. When using J2AWT, you must tweak the native DLL or shared library for every platform on which it is used.

Here's an excerpt taken from one of JDK 1.3's header files that explains the background and the reasons behind the new API:

The AWT native interface allows a native C or C++ application a means by which to access native structures in AWT. This is to facilitate moving legacy C and C++ applications to Java and to target the need ... [for these applications] to do their own native rendering to canvases for performance reasons.

Up until JDK 1.3, there was no defined way for Java programs to access the handles of underlying peer GUI components. With JDK 1.3, Sun has created a standard mechanism by which developers can allow native GUI APIs and libraries to render inside a Java AWT Canvas object. This means that there is now an official, supported way to obtain the information that supports this functionality. And when JDK 1.3 is ported to other platforms, all the ports will expose the same information -- no matter what system is being used. JDK 1.3 for Windows and JDK 1.3 for Solaris are the first implementations to provide this support.

Sun is introducing this feature for several reasons. First, it allows complex legacy software with third-party dependencies to be migrated to Java before those dependencies themselves complete migration. The second reason is performance; if the native GUI code has been fine-tuned over a long period of time with huge effort, then it makes business sense to leave it unchanged.

In this article, I will introduce the concepts behind this feature. I'll develop a sample widget step by step, which uses the Win32 API to do the rendering. Here is a snapshot of the final widget, a circular window with a smiley face.

The sample widget in action.

A step-by-step overview

The first step is to define a new Java class --

MyWindow

, say -- that extends

Canvas

and overrides the

paint

method. You use the

paint

method to draw operations for AWT objects, and override it using the keyword

native

. Overriding it in this way allows you to use your own native code. You must build your native code and compile it into a dynamic link library (DLL), just as you would with any other JNI application -- we'll call the library

MyWindow.DLL

in this case. The equivalent for Solaris or Linux would be a shared object/library. You also need to load this library into your Java class

MyWindow

, using a

System.loadLibrary("MyWindow")

call.

The two elements you'll need in order to do this are MyWindow.Java, which provides the class that extends the Canvas class, and MyWindow.CPP, which contains the JNI-based entry point for the paint routine. You can find MyWindow.Java, MyWindow.CPP, and a batch file for automating the BUILD.BAT build in the Resources section below.

Step 1: Create the MyWindow Java class

There is one major limitation that J2AWT imposes on this process: the native code can only work on a class that extends the

java.awt.Canvas

class. This is why

MyWindow

extends

Canvas

, as shown below.

MyWindow

can be used just like any other

Canvas

-derived class in your Java application; in our example, I've added

MyWindow

to

Jwindow

.

import java.awt.*; import javax.swing.*;

public class MyWindow extends Canvas {

static {

// Load the library that contains the paint code.

System.loadLibrary("MyWindow");

} // native entry point for Painting

public native void paint(Graphics g); public static void main( String[] argv ){

Frame f = new Frame();

f.setSize(300,400); JWindow w = new JWindow(f);

w.setBackground(new Color(0,0,0,255));

w.getContentPane().setBackground(new Color(0,0,0,255));

w.getContentPane().add(new MyWindow());

w.setBounds(300,300,300,300);

w.setVisible(true);

} }

Notice that you load MyWindow.DLL in the static block. This is how your Java application gets access to the native code. (I'll develop that native code momentarily.) Also notice that the paint method is declared native and has no implementation; this let's the virtual machine know that it is supposed to call the native method from the DLL loaded in the static block.

Step 2: Generate the class's JNI header file To generate the JNI header file for the class defined above, you need to use the command javah MyWindow.class. First, you should make sure the class file is in your CLASSPATH. Here's part of the generated MyWindow.h, which shows the function declaration.

/* * Class: MyWindow * Method: paint * Signature: (Ljava/awt/Graphics;)V */ JNIEXPORT void JNICALL Java_MyWindow_paint

(JNIEnv *, jobject, jobject);

Step 3: Develop the complete MyWindow.CPP

Here is the complete

MyWindow.CPP

, which has the native code for the paint routine needed in

MyWindow.Java

.

#include <windows.h>
#include <assert.h>
#include "jawt_md.h"
#include "MyWindow.h"
#define X(x) (int)(xLeft + (x)*xScale/100)  // Scaling macros
#define Y(y) (int)(yTop + (y)*yScale/100)   // so scale is 0 - 100
#define CX(x) (int)((x)*xScale/100)
#define CY(y) (int)((y)*yScale/100)
void DrawSmiley(HWND hWnd, HDC hdc);
HRGN hrgn = NULL;
JNIEXPORT void JNICALL
Java_MyWindow_paint(JNIEnv* env, jobject canvas, jobject graphics)
{
        JAWT awt;
        JAWT_DrawingSurface* ds;
        JAWT_DrawingSurfaceInfo* dsi;
        JAWT_Win32DrawingSurfaceInfo* dsi_win;
        jboolean result;
        jint lock;
        // Get the AWT
        awt.version = JAWT_VERSION_1_3;
        result = JAWT_GetAWT(env, &awt);
        assert(result != JNI_FALSE);
        // Get the drawing surface
        ds = awt.GetDrawingSurface(env, canvas);
        if(ds == NULL)
            return;
        // Lock the drawing surface
        lock = ds->Lock(ds);
        assert((lock & JAWT_LOCK_ERROR) == 0);
        // Get the drawing surface info
        dsi = ds->GetDrawingSurfaceInfo(ds);
        // Get the platform-specific drawing info
        dsi_win = (JAWT_Win32DrawingSurfaceInfo*)dsi->platformInfo;
        HDC hdc = dsi_win->hdc;
        HWND hWnd = dsi_win->hwnd;
        //////////////////////////////
        // !!! DO PAINTING HERE !!! //
        //////////////////////////////
        if(hrgn == NULL)
        {
                RECT rcBounds;
                GetWindowRect(hWnd,&rcBounds);
                long xLeft = 0;         // Use with scaling macros
                long yTop = 0;
                long xScale = rcBounds.right-rcBounds.left;
                long yScale = rcBounds.bottom-rcBounds.top;
                hrgn = CreateEllipticRgn(X(10), Y(15), X(90), Y(95));
                SetWindowRgn(GetParent(hWnd),hrgn,TRUE);
                InvalidateRect(hWnd,NULL,TRUE);
        } else {
                DrawSmiley(hWnd,hdc);
        }
        // Free the drawing surface info
        ds->FreeDrawingSurfaceInfo(dsi);
        // Unlock the drawing surface
        ds->Unlock(ds);
        // Free the drawing surface
        awt.FreeDrawingSurface(ds);
}
void DrawSmiley(HWND hWnd, HDC hdc)
{
        RECT rcBounds;
        GetWindowRect(hWnd,&rcBounds);
        long xLeft = 0;         // Use with scaling macros
        long yTop = 0;
        long xScale = rcBounds.right-rcBounds.left;
        long yScale = rcBounds.bottom-rcBounds.top;
        // Pen width based on control size
        int iPenWidth = max(CX(5), CY(5));
        HBRUSH brushBlack;
        HBRUSH brushYellow;
        HPEN penBlack = CreatePen(PS_SOLID, iPenWidth, RGB(0x00,0x00,0x00));
        // Null pen for drawing filled ellipses
        HPEN penNull = CreatePen(PS_NULL, 0, (COLORREF)0);
        brushBlack = CreateSolidBrush(RGB(0x00,0x00,0x00));
        brushYellow = CreateSolidBrush(RGB(0xff,0xff,0x00));
        HPEN pPenSave = (HPEN)SelectObject(hdc, penBlack);
        HBRUSH pBrushSave = (HBRUSH)SelectObject(hdc,brushYellow);
        Ellipse(hdc,X(10), Y(15), X(90), Y(95));       // Head
        Arc(hdc,X(25), Y(10), X(75), Y(80),            // Smile mouth
           X(35), Y(70), X(65), Y(70));
        SelectObject(hdc,&penNull);                    // No draw width
        SelectObject(hdc,&brushBlack);
        Ellipse(hdc,X(57), Y(35), X(65), Y(50));
        Ellipse(hdc,X(35), Y(35), X(43), Y(50));       // Right eye
        Ellipse(hdc,X(46), Y(50), X(54), Y(65));       // Nose
        SetBkMode(hdc,TRANSPARENT);                    // Use ForeColor
        SelectObject(hdc,pBrushSave);
        SelectObject(hdc,pPenSave);
}

The key data structure here is JAWT, which is defined in jawt.h (included with jawt_md.h). It provides access to all the information the native code needs be able to draw on the Java-based GUI component. The first part of the native method is boilerplate: it populates the JAWT structure, gets a JAWT_Win32DrawingSurfaceInfo structure, locks the surface (only one drawing engine at a time, please!), and then gets a JAWT_DrawingSurfaceInfo structure that contains a pointer (in the platformInfo field) to the necessary platform-specific drawing information. It also includes the bounding rectangle of the drawing surface and the current clipping region. For more information, dig into jawt.h and jawt_md.h (see the section entitled "Build the environment," below).

Java_MyWindow_paint is the entry point that the JVM will invoke for drawing the MyWindow. The helper function DrawSmiley does the actual rendering using Win32 calls. To include GetDrawingSurfaceInfo in your application, use the jawt.lib external library (see "Build the environment").

Step 4: Edit BUILD.BAT

Edit

BUILD.BAT

before running it and set the path for your Visual C++ 6.0 and JDK 1.3 directories as shown below. The

BUILD.BAT

compiles

MyWindow.java

, generates

MyWindow.h

, and then compiles

MyWindow.CPP

into

MyWindow.DLL

.

SET DEVSTUDIO=D:\Program Files\Microsoft Visual Studio\VC98
SET JDK13=D:\JDK1.3

That's it; you are ready to go. Before running the sample, make sure that MyWindow.DLL, \JDK1.3\BIN, and \JDK1.3\JRE\BIN are in your PATH, and that the current directory is in CLASSPATH; this will guarantee that MyWindow.class will load successfully. After making sure that the PATH and CLASSPATH are set properly, enter java MyWindow on the command line to run the application. A RUN.BAT batch file is included in window.zip (see Resources) for your convenience. Edit RUN.BAT to set the PATH and CLASSPATH for JDK 1.3.

Build the environment

  • Headers: New C headers for the Windows platform have been added to the JDK's include directory. They are:

    include/jawt.h.

    include/win32/jawt_md.h.

    According to the JavaSoft Website, these headers are not part of the Java 2 Platform's official specification; rather, they are provided as a convenience to developers who want a standardized way to access native drawing functionality. I think this means that people who port the JDK to other platforms can choose not to expose this API.

  • Libraries: A new library, named jawt.lib, has been added to the SDK's library directory. As explained earlier, it has the entry points needed for including J2AWT in your application. For example, to link to the GetDrawingSurfaceInfo entry point, you need to include jawt.lib in your build.
  • Tools: The javah tool is used to generate the C/C++ header file for the native functions of the Java class, and the javac tool is used to compile the Java source.

Conclusion

Migrating a legacy software system to Java is a nontrivial task, especially if it includes a high-performance rendering engine. The Java 2 AWT Native Interface makes it easier to migrate in stages, allowing code that is not performance sensitive to be migrated before the critical rendering code. It also makes third-party widget developers more likely to take a serious look at developing products for Java. With the Java 2 AWT Native API, you can port existing legacy GUI code and finish development more quickly, without sacrificing your investment in the performance of a key piece of native code.

Davanum Srinivas is currently a developer at Computer Associates.

Learn more about this topic

Recommended
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more