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 Solaris, 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 Solaris, against the
jre/lib/sparc/libjawt.so
library. (For 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 Windows platform would be
structured similarly, but would include the version of jawt_md.h
for Windows 32 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 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 Win32 and Solaris support the existing structures in jawt_md.h.
*
*******************
* EXAMPLE OF USAGE:
*******************
*
* In Win32, 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 Windows or a
* JAWT_X11DrawingSurfaceInfo on Solaris. 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 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 (Windows 32 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
/*
* Win32-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_ */