AWT Native Interface

   

ネイティブコードから AWT Canvas へのレンダリングを行う際の基本事項

新しい JavaTM 2 AWT Native Interface を使用すると、レンダリングライブラリをネイティブコードにコンパイルして、Java Canvas 描画面に直接描画できます。その結果、これらのライブラリを Java に変換することなく、またパフォーマンスを著しく低下させることなく使用できます。このドキュメントには、AWT Native Interface の使用が簡単であることを示す例、およびその技術的な解説が含まれています。

はじめに

Java 2 Platform, Standard Edition の定義には、JNI (Java Native Interface) が含まれます。Java 開発者の大半は JNI を使用することはありませんが、Java バイトコードが、ホストマイクロプロセッサ用のネイティブな機械命令にコンパイル済みのアプリケーションコードと直接やり取りする唯一の方法であるため、ある状況ではこのインタフェースは非常に重要です。JNI の使用法として最も多いのは、Java プログラミング言語がまだサポートしないプラットフォーム機能へのアクセスを可能にする「エスケープバルブ」としての働きです。たとえば、JNI を使って、USB ポート経由でシステムに接続されたスキャナなどの周辺機器と通信を行うネイティブコードを統合できます。

JNI は汎用なため、実行すべきタスクが Pure Java を使って実現できるかどうかにかかわらず、ほぼあらゆる種類のネイティブライブラリへのアクセスに使用できます。JNI を使用することによるデメリットは、コードの移植性が損なわれることですが、これはビジネス上および技術的な観点から受け入れられる、または必要でさえあるものと言えます。

ビジネス的観点については、従来のソフトウェアを Java に移植しようとしている場合を考えてみてください。そのソフトウェアは重要な処理にサードパーティ製のライブラリを使用しています。ソースコードを使用する権限を持っておらず、Java 版を提供するようサードパーティを説得できない場合には、それを使用することはできなくなります。ソースを入手できたとしても、標準ライブラリの Java への移植およびテストには、膨大なコストがかかります。

ネイティブコードに手を加えない別の重要な理由は、パフォーマンスにあります。パフォーマンスを向上させるため何年もかけてチューニングされたコードを利用している場合、それを Java に変換してパフォーマンスを低下させるようなことは望まないでしょう。通常、Java の移植性とコード保守管理の容易さが、パフォーマンス面でのデメリットを補って余りあると判断するまで、ネイティブコードをそのまま使用するのが最善です。

描画ライブラリは、大半の開発者がパフォーマンス上の理由でそのまま使用するネイティブコードの良い例です。残念なことに、描画ライブラリは、JNI を介して Java コードと統合することの最も難しいライブラリの 1 つです。基本的な問題は、描画用コードがどこに描画すべきかを知らないことにあります。このため、Java 描画面に関する情報 (Canvas の基盤となるピアへのハンドルなど) にアクセスする必要がありますが、それを行うことはできません。

現在まで、Java プラットフォームからこのプライベートな情報へのアクセスが可能でした。ここで言う「プライベート」とは、ドキュメント化されていない、サポートされていない、推奨されていないという意味です。西暦 2000 年初頭の Java 2 アップグレードリリース (Kestrel) で「AWT Native Interface」が導入されることは、こうした状況が変化することを示す良い知らせです。これで、初めて公式に Java Canvas に関するすべての情報を入手できるようになるため、オペレーティングシステムの提供する描画ルーチンを使って、ネイティブコードライブラリから Canvas に直接描画できるようになります。

稼動方法

このセクションでは、AWT Native Interface の最も一般的な使用法について説明します。具体的には、paint メソッドをオーバーライドしてネイティブ描画ライブラリに描画操作を指示すると、ネイティブ描画ライブラリが Java VM に問い合わせて、描画に必要な情報を取得します。ただし、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 ツールを実行し、Java が使用するネイティブの paint メソッドへのインタフェースが記述された C/C++ ヘッダファイルを生成することです。javah は、Java 2 SDK に含まれる標準ツールです。

最後のステップは最も興味深いもので、javah が生成したヘッダファイルに準拠したインタフェースを使用して、ネイティブ描画メソッドを記述し、Solaris の場合は、それを jre/lib/sparc/libjawt.so ライブラリにリンクさせることにより、標準共有ライブラリ (上の例では myRenderingLib) として構築します。(Windows の場合は、jre/bin/jawt.dll ライブラリにリンクします。)このコードは、Java 仮想マシンへのコールバックを行なって、MyCanvas ピアへのアクセスに必要な描画面情報を取得します。この情報が利用可能になると、コードは、基盤となるオぺレーティングシステムの提供する標準描画ルーチンを使用して、MyCanvas に直接描画できるようになります。

以下に示すネイティブ paint メソッドのソースコードは、Solaris X11 ベースの描画環境、および AWT Native Interface の存在する Java VM での使用を念頭に置いて設計されたものです。

/*
 * 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);
}

ここでの重要なデータ構造は、jawt.h (jawt_md.h に含まれる) で定義された JAWT です。JAWT を利用することで、ジョブの実行にネイティブコードが必要とするすべての情報へのアクセスが可能になります。ネイティブメソッドの最初の部分は常用文で、JAWT 構造の生成、JAWT_DrawingSurface 構造の取得、描画面のロック (一度に 1 つの描画エンジンだけ) を実行してから、必要なプラットフォーム固有の描画情報へのポインタを (platformInfo フィールド内に) 含む JAWT_DrawingSurfaceInfo 構造を取得します。この部分には、描画面の境界の矩形および現在のクリッピング領域も含まれます。

platformInfo で指されている情報の構造は、 jawt_md.h と呼ばれるマシン依存のヘッダファイルで定義されます。Solaris/X11 の描画では、MyCanvas に関連付けられた X11 ディスプレイと X11 ドロアブルの情報も含まれます。描画操作の完了後に、JAWT_DrawingSurfaceInfo の解放、および JAWT_DrawingSurface のロック解除および解放が行われるため、より多くの常用文が存在することになります。

Windows プラットフォームでも対応するコードを同様の方法で構築できます。ただし、含まれるのは Windows 32 用の jawt_md.h であること、および描画面情報の platformInfo フィールド内の構造は、JAWT_Win32DrawingSurfaceInfo* としてキャストされることが異なります。また、実際の描画操作は、Windows プラットフォームに適したものに変更する必要があります。

まとめ

ネイティブコードライブラリから Java Canvas に直接描画する機能は、従来の (特に高性能の描画エンジンを含む) ソフトウェアシステムを Java に移行させることを計画している開発者には非常に有用です。これにより、パフォーマンスに影響を与える描画用コードには手を加えず、ほかのコードを Java に変換するという方法で段階的に移行することが容易になります。その結果、Java 中心のアプリケーションによる高い移植性と開発効率という恩恵を享受しつつ、ネイティブコードの活用によりパフォーマンスへの投資を無駄にしないで済みます。

リファレンス

Java ネイティブインタフェースに関しては、Sun Microsystems, Inc. の 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

/*
 * @(#)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 <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 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 版)

/*
 * @(#)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 <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 (Windows 32 版)

/*
 * @(#)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 <windows.h>
#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_ */

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

Sun