The AWT Native Interface

   

The Key to Rendering to an AWT Canvas from Native Code

The new JavaTM 2 AWT Native Interface enables rendering libraries compiled to native code to draw directly to a Java Canvas drawing surface. This means that such libraries can be used without being converted to Java first and without significant impact on performance. An example illustrating how easy it is to use the AWT Native Interface is presented and discussed in this technical note.

Introduction

The definition of Java 2 Platform, Standard Edition includes JNI, the Java Native Interface. Most Java developers will never need to use it, but the interface is invaluable in certain situations because it provides the only way for Java byte code to interact directly with application code that has been compiled to the native machine instructions for the host microprocessor. JNI is most often used as an ``escape valve'' to enable access to platform functionality not yet supported by the Java programming language. For example, you could use JNI to integrate with native code that communicates with a peripheral device, such as a scanner, connected to a system via a USB port.

Of course, JNI is general enough to be used to access almost any sort of native library, regardless of whether the task to be accomplished could also be done using pure Java. The major penalty for using it is that code portability suffers, but this may be acceptable or even necessary for business or technical reasons.

Business reasons? Consider the situation where the legacy software you're trying to port to Java uses a third-party library for a critical set of operations. If you don't have source rights to this library and you can't convince the owner to provide a Java version, you may not be able to use it. Even if you do have the source, the effort needed to port a standard library to Java and test it may be too expensive to consider.

Another important reason for leaving the native code alone is related to performance. If you're dealing with a finely crafted piece of code, carefully tuned for performance over the course of years, you probably do not want to convert it to Java and risk a performance penalty. It's usually best to keep it intact until you're satisfied that the benefits of Java portability and code maintainability outweigh the expected performance difference.

A rendering library is a good example of a piece of native code that most developers would just as soon leave alone for performance reasons. Unfortunately, this is the very type of library that has been most difficult to integrate with Java code through JNI. The fundamental problem has been that the rendering code doesn't know where to draw! It needs access to information about a Java drawing surface (such as a handle to the underlying peer of a Canvas), but can't get it.

Until now, the Java platform has kept access to this information private -- ``private'' in the sense of undocumented, unsupported, and deprecated. The good news is that this situation will be remedied with the introduction of the ``AWT Native Interface'' in the Java 2 upgrade release (``Kestrel'') expected in early 2000. For the first time there will be an official way to obtain all the information you need to know about the peer of a Java Canvas so that you can draw directly to the Canvas from a native code library using the drawing routines provided by the operating system.

How It Works

In this section we'll describe the most common usage of the AWT Native Interface -- overriding the paint method to direct drawing operations to a native rendering library which then queries the Java VM to determine the information it needs in order to render. Note, however, that any native code may use the AWT Native Interface to learn about a target drawing surface, not just code in a paint method.

The first step in hooking up a native rendering library to a Java Canvas is to define a new class that extends Canvas and overrides the paint method. The Java system routes all drawing operations for a Canvas object through the paint method, as it does for all other GUI objects.

The new paint method, to be implemented in the native rendering library, must be declared as public native void , and the native library itself is loaded at runtime by including a call to System.loadLibrary( "myRenderingLib") in the static block of the class. ``myRenderingLib'' is the name we're using for the native shared library; for the Solaris operating environment, the actual name for the library file on disk is libmyRenderingLib.so .

Here's a simple example of such a class:

import java.awt.*;
import java.awt.event.*;

public class MyCanvas extends Canvas {
    static {
        System.loadLibrary("myRenderingLib");
    }
    public native void paint(Graphics g);

    public static void main(String[] args) {
        Frame f = new Frame();
        f.setBounds(0, 0, 500, 110);
        f.add( new MyCanvas() );
        f.addWindowListener( new WindowAdapter() {
            public void windowClosing(WindowEvent ev) {
                System.exit(0);
            }
        } );
        f.show();
    }
}

Note that this class has a main method that can be used torun this code as an application for testing purposes.

The next step is to run the javah tool on the MyCanvas class file above to generate a C/C++ header file that describes the interface to the native paint method that Java expects to be used. javah is a standard tool included with the Java 2 SDK.

The final step ­ the most interesting one ­ is to write the native rendering method, with an interface that conforms to the header file that javah generated, and build it as a standard shared library (called myRenderingLib in the above example) by linking it, for the Solaris operating environment, against the jre/lib/sparc/libjawt.so library. (For Microsoft Windows, link against the jre/bin/jawt.dll library.) This code will call back to the Java virtual machine to get the drawing surface information it needs to access the MyCanvas peer. Once this information is available, the code can draw directly to MyCanvas using standard drawing routines supplied by the underlying operating system.

Here is sample source code for a native paint method designed for use in a Solaris X11-based drawing environment and a Java VM where the AWT Native Interface is present:

/*
 * Copyright 1999 Sun Microsystems, Inc.,
 * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
 * All rights reserved.
 * 
 */

#include "MyCanvas.h"
#include "jawt_md.h"

/*
 * Class:     MyCanvas
 * Method:    paint
 * Signature: (Ljava/awt/Graphics;)V
 */
JNIEXPORT void JNICALL Java_MyCanvas_paint
(JNIEnv* env, jobject canvas, jobject graphics)
{
    JAWT awt;
    JAWT_DrawingSurface* ds;
    JAWT_DrawingSurfaceInfo* dsi;
    JAWT_X11DrawingSurfaceInfo* dsi_x11;
    jboolean result;
    jint lock;
    GC gc;
    
    short	i;
    char	*testString = "^^^ rendered from native code ^^^";

    /* Get the AWT */
    awt.version = JAWT_VERSION_1_3;
    if (JAWT_GetAWT(env, &awt) == JNI_FALSE) {
        printf("AWT Not found\n");
        return;
    }

    /* Get the drawing surface */
    ds = awt.GetDrawingSurface(env, canvas);
    if (ds == NULL) {
        printf("NULL drawing surface\n");
        return;
    }

    /* Lock the drawing surface */
    lock = ds->Lock(ds);
    if((lock & JAWT_LOCK_ERROR) != 0) {
        printf("Error locking surface\n");
        awt.FreeDrawingSurface(ds);
        return;
    }

    /* Get the drawing surface info */
    dsi = ds->GetDrawingSurfaceInfo(ds);
    if (dsi == NULL) {
        printf("Error getting surface info\n");
        ds->Unlock(ds);
        awt.FreeDrawingSurface(ds);
        return;
    }

    /* Get the platform-specific drawing info */
    dsi_x11 = (JAWT_X11DrawingSurfaceInfo*)dsi->platformInfo;


    /* Now paint */
    gc = XCreateGC(dsi_x11->display, dsi_x11->drawable, 0, 0);
    XSetBackground(dsi_x11->display, gc, 0);
    for (i=0; i<36;i++)
    {
	XSetForeground(dsi_x11->display, gc, 10*i);
   	XFillRectangle(dsi_x11->display, dsi_x11->drawable, gc,
                   	10*i, 5, 90, 90);
    }
    XSetForeground(dsi_x11->display, gc, 155);
    XDrawImageString(dsi_x11->display, dsi_x11->drawable, gc,
    			100, 110, testString, strlen(testString));
    XFreeGC(dsi_x11->display, gc);


    /* Free the drawing surface info */
    ds->FreeDrawingSurfaceInfo(dsi);

    /* Unlock the drawing surface */
    ds->Unlock(ds);

    /* Free the drawing surface */
    awt.FreeDrawingSurface(ds);
}

The key data structure here is JAWT , which is defined in jawt.h (included by jawt_md.h) ; it provides access to all the information the native code needs to get the job done. The first part of the native method is boilerplate: it populates the JAWT structure, gets a JAWT_DrawingSurface structure, locks the surface (only one drawing engine at a time, please!), 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.

The structure of the information pointed to by platformInfo is defined in a machine-dependent header file called jawt_md.h. For Solaris/X11 drawing, it includes information about the X11 display and X11 drawable associated with MyCanvas. After the drawing operations are completed, there is more boilerplate code as JAWT_DrawingSurfaceInfo is freed and JAWT_DrawingSurface is unlocked and freed.

The corresponding code for the Microsoft Windows platform would be structured similarly, but would include the version of jawt_md.h for Microsoft Windows and the structure located in the platformInfo field of drawing surface info would be cast as a JAWT_Win32DrawingSurfaceInfo* . And, of course, the actual drawing operations would need to be changed to those appropriate for the Microsoft Windows platform.

Summary

The ability to draw directly into a Java Canvas from a native code library is extremely useful for developers planning to migrate a legacy software system to Java, especially one that includes a high-performance rendering engine. It makes it much easier to migrate in stages, leaving performance-sensitive rendering code alone, while other less-sensitive portions of code are converted to Java. The result can be a modern Java-centric application, providing the benefit of portability and development efficiency, but one that does not sacrifice an investment in performance of a key piece of native code.

References

The definitive reference to the Java Native Interface is The Java Native Interface: Programmer's Guide and Specification by Sheng Liang of Sun Microsystems. This book was published in June 1999 by Addison-Wesley. The ISBN is 0-201-32577-2.

Appendix

Header Files for jawt.h and jawt_md.h

jawt.h

/*
 * @(#)jawt.h	1.2 99/05/27
 *
 * Copyright 1999 by Sun Microsystems, Inc.,
 * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information
 * of Sun Microsystems, Inc. ("Confidential Information").  You
 * shall not disclose such Confidential Information and shall use
 * it only in accordance with the terms of the license agreement
 * you entered into with Sun.
 */

#ifndef _JAVASOFT_JAWT_H_
#define _JAVASOFT_JAWT_H_

#include "jni.h"

#ifdef __cplusplus
extern "C" {
#endif

/*
 * AWT native interface (new in JDK 1.3)
 *
 * 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 needs of the
 * community who, at present, wish to do their own native rendering to canvases
 * for performance reasons.  Standard extensions such as Java3D also require a
 * means to access the underlying native data structures of AWT.
 *
 * There may be future extensions to this API depending on demand.
 *
 * A VM does not have to implement this API in order to pass the JCK.
 * It is recommended, however, that this API is implemented on VMs that support
 * standard extensions, such as Java3D.
 *
 * Since this is a native API, any program which uses it cannot be considered
 * 100% pure java.
 */

/*
 * AWT Native Drawing Surface (JAWT_DrawingSurface).
 *
 * For each platform, there is a native drawing surface structure.  This
 * platform-specific structure can be found in jawt_md.h.  It is recommended
 * that additional platforms follow the same model.  It is also recommended
 * that VMs on Microsoft Windows platforms and Solaris operating environment 
 * support the existing structures in jawt_md.h.
 *
 *******************
 * EXAMPLE OF USAGE:
 *******************
 *
 * In Miscrosoft Windows, a programmer wishes to access the HWND of a canvas 
 * to perform native rendering into it.  The programmer has declared the 
 * paint() method for their canvas subclass to be native:
 *
 *
 * MyCanvas.java:
 *
 * import java.awt.*;
 *
 * public class MyCanvas extends Canvas {
 *
 *     static {
 *         System.loadLibrary("mylib");
 *     }
 *
 *     public native void paint(Graphics g);
 * }
 *
 *
 * myfile.c:
 *
 * #include "jawt_md.h"
 * #include 
 *
 * JNIEXPORT void JNICALL
 * Java_MyCanvas_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);
 *     assert(ds != NULL);
 *
 *     // 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;
 *
 *     //////////////////////////////
 *     // !!! DO PAINTING HERE !!! //
 *     //////////////////////////////
 *
 *     // Free the drawing surface info
 *     ds->FreeDrawingSurfaceInfo(dsi);
 *
 *     // Unlock the drawing surface
 *     ds->Unlock(ds);
 *
 *     // Free the drawing surface
 *     awt.FreeDrawingSurface(ds);
 * }
 *
 */

/*
 * JAWT_Rectangle
 * Structure for a native rectangle.
 */
typedef struct jawt_Rectangle {
    jint x;
    jint y;
    jint width;
    jint height;
} JAWT_Rectangle;

struct jawt_DrawingSurface;

/*
 * JAWT_DrawingSurfaceInfo
 * Structure for containing the underlying drawing information of a component.
 */
typedef struct jawt_DrawingSurfaceInfo {
    /*
     * Pointer to the platform-specific information.  This can be safely
     * cast to a JAWT_Win32DrawingSurfaceInfo on Microsoft Windows or a
     * JAWT_X11DrawingSurfaceInfo on Solaris operating environment.  
     * See jawt_md.h for details.
     */
    void* platformInfo;
    /* Cached pointer to the underlying drawing surface */
    struct jawt_DrawingSurface* ds;
    /* Bounding rectangle of the drawing surface */
    JAWT_Rectangle bounds;
    /* Number of rectangles in the clip */
    jint clipSize;
    /* Clip rectangle array */
    JAWT_Rectangle* clip;
} JAWT_DrawingSurfaceInfo;

#define JAWT_LOCK_ERROR                 0x00000001
#define JAWT_LOCK_CLIP_CHANGED          0x00000002
#define JAWT_LOCK_BOUNDS_CHANGED        0x00000004
#define JAWT_LOCK_SURFACE_CHANGED       0x00000008

/*
 * JAWT_DrawingSurface
 * Structure for containing the underlying drawing information of a component.
 * All operations on a JAWT_DrawingSurface MUST be performed from the same
 * thread as the call to GetDrawingSurface.
 */
typedef struct jawt_DrawingSurface {
    /* Cached reference to the Java environment of the calling thread */
    JNIEnv* env;
    /* Cached reference to the target object */
    jobject target;
    /*
     * Lock the surface of the target component for native rendering.
     * When finished drawing, the surface must be unlocked with
     * Unlock().  This function returns a bitmask with one or more of the
     * following values:
     *
     * JAWT_LOCK_ERROR - When an error has occurred and the surface could not
     * be locked.
     *
     * JAWT_LOCK_CLIP_CHANGED - When the clip region has changed.
     *
     * JAWT_LOCK_BOUNDS_CHANGED - When the bounds of the surface have changed.
     *
     * JAWT_LOCK_SURFACE_CHANGED - When the surface itself has changed
     */
    jint (JNICALL *Lock)
        (struct jawt_DrawingSurface* ds);
    /*
     * Get the drawing surface info.
     * The value returned may be cached, but the values may change if
     * additional calls to Lock() or Unlock() are made.
     * Lock() must be called before this can return a valid value.
     * Returns NULL if an error has occurred.
     * When finished with the returned value, FreeDrawingSurfaceInfo must be
     * called.
     */
    JAWT_DrawingSurfaceInfo* (JNICALL *GetDrawingSurfaceInfo)
        (struct jawt_DrawingSurface* ds);
    /*
     * Free the drawing surface info.
     */
    void (JNICALL *FreeDrawingSurfaceInfo)
        (JAWT_DrawingSurfaceInfo* dsi);
    /* 
     * Unlock the drawing surface of the target component for native rendering.
     */
    void (JNICALL *Unlock)
        (struct jawt_DrawingSurface* ds);
} JAWT_DrawingSurface;

/*
 * JAWT
 * Structure for containing native AWT functions.
 */
typedef struct jawt {
    /*
     * Version of this structure.  This must always be set before
     * calling JAWT_GetAWT()
     */
    jint version;
    /*
     * Return a drawing surface from a target jobject.  This value
     * may be cached.
     * Returns NULL if an error has occurred.
     * Target must be a java.awt.Canvas.
     * FreeDrawingSurface() must be called when finished with the
     * returned JAWT_DrawingSurface.
     */
    JAWT_DrawingSurface* (JNICALL *GetDrawingSurface)
        (JNIEnv* env, jobject target);
    /*
     * Free the drawing surface allocated in GetDrawingSurface.
     */
    void (JNICALL *FreeDrawingSurface)
        (JAWT_DrawingSurface* ds);
} JAWT;

/*
 * Get the AWT native structure.  This function returns JNI_FALSE if
 * an error occurs.
 */
_JNI_IMPORT_OR_EXPORT_
jboolean JNICALL JAWT_GetAWT(JNIEnv* env, JAWT* awt);

#define JAWT_VERSION_1_3 0x00010003

#ifdef __cplusplus
} /* extern "C" */
#endif

#endif /* !_JAVASOFT_JAWT_H_ */

jawt_md.h (Solaris/X11 operating environment version)

/*
 * @(#)jawt_md.h	1.2 99/05/27
 *
 * Copyright 1999 by Sun Microsystems, Inc.,
 * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information
 * of Sun Microsystems, Inc. ("Confidential Information").  You
 * shall not disclose such Confidential Information and shall use
 * it only in accordance with the terms of the license agreement
 * you entered into with Sun.
 */

#ifndef _JAVASOFT_JAWT_MD_H_
#define _JAVASOFT_JAWT_MD_H_

#include 
#include 
#include 
#include "jawt.h"

#ifdef __cplusplus
extern "C" {
#endif

/*
 * X11-specific declarations for AWT native interface.
 * See notes in jawt.h for an example of use.
 */
typedef struct jawt_X11DrawingSurfaceInfo {
    Drawable drawable;
    Display* display;
    VisualID visualID;
    Colormap colormapID;
    int depth;
} JAWT_X11DrawingSurfaceInfo;

#ifdef __cplusplus
}
#endif

#endif /* !_JAVASOFT_JAWT_MD_H_ */

jawt_md.h (Microsoft Windows version)

/*
 * @(#)jawt_md.h	1.2 99/05/27
 *
 * Copyright 1999 by Sun Microsystems, Inc.,
 * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information
 * of Sun Microsystems, Inc. ("Confidential Information").  You
 * shall not disclose such Confidential Information and shall use
 * it only in accordance with the terms of the license agreement
 * you entered into with Sun.
 */

#ifndef _JAVASOFT_JAWT_MD_H_
#define _JAVASOFT_JAWT_MD_H_

#include 
#include "jawt.h"

#ifdef __cplusplus
extern "C" {
#endif

/*
 * Microsoft Windows specific declarations for AWT native interface.
 * See notes in jawt.h for an example of use.
 */
typedef struct jawt_Win32DrawingSurfaceInfo {
    /* Native window, DDB, or DIB handle */
    union {
        HWND hwnd;
        HBITMAP hbitmap;
        void* pbits;
    };
    /*
     * This HDC should always be used instead of the HDC returned from
     * BeginPaint() or any calls to GetDC().
     */
    HDC hdc;
    HPALETTE hpalette;
} JAWT_Win32DrawingSurfaceInfo;

#ifdef __cplusplus
}
#endif

#endif /* !_JAVASOFT_JAWT_MD_H_ */

Copyright © 1999 Sun Microsystems, Inc. All Rights Reserved.

Sun