新しいJava SE AWT Native Interfaceを使用すると、ネイティブ・コードにコンパイルされたレンダリング・ライブラリを直接Java Canvasレンダリング・サーフェスにレンダリングできます。これは、そのようなライブラリを最初にJavaに変換することなく、またパフォーマンスに大きな影響することなく使用できることを意味します。このテクニカル・ノートでは、AWT Native Interfaceがどれほど簡単に使用できるかを示す例を示し、説明します。
はじめに
Java Standard Editionの定義には、JNI (Java Native Interface)が含まれます。Java開発者のほとんどは使用する必要がありませんが、このインタフェースはある状況では非常に重要になります。Javaバイト・コードがホスト・マイクロプロセッサ用にネイティブ機械命令にコンパイルされたアプリケーション・コードと直接やり取りする場合に、これが唯一の方法を提供するためです。JNIは、Javaプログラミング言語によってまだサポートされていないプラットフォーム機能にアクセスできるようにするための「エスケープ・バルブ」として、もっともよく使われます。たとえば、JNIを使用して、USBポート経由でシステムに接続されたスキャナなどの周辺機器とやり取りするネイティブ・コードと統合できます。
もちろん、JNIは十分に汎用性を持つので、ほぼあらゆる種類のネイティブ・ライブラリにアクセスするために使用できます(達成すべきタスクがPure Javaで実行できるかどうかにかかわらず)。これを使用することの主要な不利益はコード移植性が損なわれることですが、これは許容できる場合もあり、むしろ業務上または技術上の理由で必要な場合さえあります。
業務上の理由とは何か。Javaに移植しようとしているレガシー・ソフトウェアが、クリティカルなオペレーション・セットのために、サード・パーティ製ライブラリを使用する状況を想定してください。あなたはこのライブラリへのソース権限を持っておらず、Java版を提供するように所有者を説得することもできない場合、それを使用できない可能性があります。ソースを持っていたとしても、標準ライブラリをJavaに移植してテストするために必要な工数はあまりに膨大すぎて見当がつかないかもしれません。
ネイティブ・コードをそのままにするもう1つの重要な理由は、パフォーマンスに関連しています。何年もかけてパフォーマンスを向上させるために慎重にチューニングされた、精巧に構築されたコードを扱っている場合、それをJavaに変換してパフォーマンス低下のリスクを招きたくないはずです。Javaの移植性およびコード保守性の利点が、予想されるパフォーマンスの違いを上回っていると納得するまでは、ネイティブ・コードはそのままにするのが普通はベストです。
レンダリング・ライブラリは、ほとんどの開発者がパフォーマンス上の理由からできればそのままにしておきたいネイティブ・コードの良い例です。このタイプのライブラリは残念なことに、JNIを介してJavaコードと統合することがもっとも難しかったライブラリです。根本的な問題は、レンダリング・コードをどこにレンダリングすべきかを識別できないことでした。Java描画サーフェスに関する情報(Canvasの基盤ピアへのハンドルなど)にアクセスする必要がありますが、それを取得できません。
これまで、Javaプラットフォームはこの情報へのアクセスを非公開にしていました−ドキュメント化されていない、サポートされていない、および推奨されていないという意味で「非公開」です。うれしいことに、Javaアップグレード・リリース(Kestrel)で「AWT Native Interface」が導入されることで、この状況が改善されます。Java Canvasのピアについて知る必要のあるすべての情報を公式に入手する方法がはじめてもたらされて、オペレーティング・システムによって提供される描画ルーチンを使ってネイティブ・コード・ライブラリからCanvasに直接描画できるようになります。
どのように使用するか
このセクションでは、AWT Native Interfaceのもっとも一般的な使用法について説明します−レンダリング・オペレーションをネイティブ・レンダリング・ライブラリにリダイレクトし、ネイティブ・レンダリング・ライブラリがレンダリングに必要な情報を確認するためにJava VMを照会するように、paintメソッドをオーバーライドします。ただし、paintメソッド内のコードだけでなくネイティブ・コードが、AWT Native Interfaceを使ってターゲット描画サーフェスに関する情報を学習することもできます。
ネイティブ・レンダリング・ライブラリをJava Canvasに接続するための最初の手順は、Canvasを継承してpaintメソッドをオーバーライドする新しいクラスを新たに定義することです。Javaシステムは、ほかのすべてのGUIオブジェクトの場合と同様に、Canvasオブジェクトのためのすべての描画オペレーションをpaintメソッドで経路指定します。
新しいpaintメソッドは、ネイティブ・レンダリング・ライブラリ内に実装するために、public native voidとして宣言する必要があります。ネイティブ・ライブラリ自体は、クラスのstaticブロック内でSystem.loadLibrary( "myRenderingLib")呼出しを含めることによって、実行時にロードされます。myRenderingLibという名前はネイティブ共有ライブラリのために使用されます。Solarisオペレーティング環境の場合は、ライブラリ・ファイルのディスク上での実際の名はlibmyRenderingLib.soです。
そのようなクラスの簡単な例を示します。
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(); } }
このクラスは、このコードをテスト目的のアプリケーションとして実行するために使用できる、mainメソッドを持っています。
次の手順は、上記のMyCanvasクラス・ファイルでjavahツールを実行して、ネイティブpaintメソッド(Javaが使用されているはず)へのインタフェースを記述するC/C++ヘッダー・ファイルを生成することです。javahは、Java 2 SDKに付属の標準ツールです。
最後の手順はもっとも興味深く、javahが生成したヘッダー・ファイルに準拠するインタフェースを使用してネイティブ・レンダリング・メソッドを記述して、それをjre/lib/sparc/libjawt.soライブラリにリンクすることで(Solarisオペレーティング環境の場合)標準共有ライブラリ(上の例でmyRenderingLibと呼ばれている)として構築します。(Microsoft Windowsの場合はjre/bin/jawt.dllライブラリにリンクします)。このコードは、MyCanvasピアへのアクセスに必要な描画サーフェス情報を取得するために、Java仮想マシンにコールバックします。この情報が利用可能になると、コードは基盤となるオペレーティング・システムによって提供される標準描画ルーチンを使用してMyCanvasに直接描画できます。
これはネイティブpaintメソッドのサンプル・ソース・コードで、Solaris X11ベースの描画環境およびAWT Native Interfaceが存在するJava VMで使用するために設計されています。
#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); }
ここでの重要なデータ構造は、jawt.h (jawt_md.hによって取り込まれる)に定義されている、JAWTです。これは、ジョブを完了するためにネイティブ・コードが必要とするすべての情報へのアクセスを提供します。ネイティブ・メソッドの最初の部分はボイラープレートです。JAWT構造を取得し、JAWT_DrawingSurface構造を取得し、サーフェスをロック(一度に1つの描画エンジンのみにしてください)してから、必要なプラットフォーム固有描画情報へのポインタ(platformInfoフィールド内)を含むJAWT_DrawingSurfaceInfo構造を取得します。描画サーフェスの境界矩形および現在のクリッピング領域も含んでいます。
platformInfoによってポイントされている情報の構造は、jawt_md.hと呼ばれるマシン依存ヘッダー・ファイルに定義されています。Solaris/X11描画の場合は、MyCanvasに関連付けられたX11ディスプレイとX11ドロアブルの情報を含みます。描画オペレーションが完了したあとに、JAWT_DrawingSurfaceInfoが解放されて、JAWT_DrawingSurfaceがロック解除および解放されると、ボイラープレート・コードが増えています。
Microsoft Windowsプラットフォームの対応するコードも同様に構築されますが、Microsoft Windows用のjawt_md.hを含み、描画サーフェス情報のplatformInfoフィールド内の構造が、JAWT_Win32DrawingSurfaceInfo*としてキャストされます。また、実際の描画オペレーションはもちろん、Microsoft Windowsプラットフォームに適したものに変更される必要があります。
サマリー
ネイティブ・コード・ライブラリからJava Canvasに直接レンダリングできることは、レガシー・ソフトウェア・システム(特に、高性能レンダリング・エンジンを含むもの)をJavaに移行することを計画している開発者にとって非常に役立ちます。これにより、段階的な移行がかなり容易になります(パフォーマンスが重要なレンダリング・コードはそのままにし、それほど重要でないその他のコード部分がJavaに変換される)。結果は、最新のJavaセントリック・アプリケーションになる可能性があります(移植性と開発効率性の利点を提供する一方、重要なネイティブ・コードのパフォーマンスへの投資を犠牲にしない)。
参照
Java Native Interfaceに関しては、Sheng Liang著『The Java Native Interface: Programmer's Guide and Specification』を参照してください。この書籍は、1999年6月にAddison-Wesleyから出版されました(ISBN: 0-201-32577-2)。
付録
jawt.hおよびjawt_md.h用のヘッダー・ファイル
jawt.h
#ifndef _JAVASOFT_JAWT_H_ #define _JAVASOFT_JAWT_H_ #include "jni.h" #ifdef __cplusplus extern "C" { #endif /* * AWT native interface * * 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 <assert.h> * * 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オペレーティング環境版)
#ifndef _JAVASOFT_JAWT_MD_H_ #define _JAVASOFT_JAWT_MD_H_ #include <X11/Xlib.h> #include <X11/Xutil.h> #include <X11/Intrinsic.h> #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版)
#ifndef _JAVASOFT_JAWT_MD_H_ #define _JAVASOFT_JAWT_MD_H_ #include <windows.h> #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_ */