AWT Native Interface

ネイティブコードから AWT Canvas にレンダリングする際の基本事項

新しい Java™ SE AWT Native Interface を使用すると、ネイティブコードにコンパイルされたレンダリングライブラリを直接 Java Canvas レンダリングサーフェスにレンダリングできます。これは、そのようなライブラリを最初に Java に変換することなく、またパフォーマンスに大きな影響することなく使用できることを意味します。このテクニカルノートでは、AWT Native Interface がどれほど簡単に使用できるかを示す例を示し、説明します。

はじめに

Java Standard Edition の定義には、JNI (Java Native Interface) が含まれます。Java 開発者のほとんどは JNI を使用する必要がありませんが、このインタフェースはある状況では非常に重要になります。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 Virtual Machine にコールバックします。この情報が利用可能になると、コードは基盤となるオぺレーティングシステムによって提供される標準描画ルーチンを使用して MyCanvas に直接描画できます。

これはネイティブ paint メソッドのサンプルソースコードで、Solaris X11 ベースの描画環境および AWT Native Interface が存在する Java VM で使用するために設計されています。

/*
 * Copyright (c) 1999 Oracle and/or its affiliates.  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_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

/*
 * @(#)jawt.h   1.2 99/05/27
 *
 * Copyright (c) 1999 Oracle and/or its affiliates.  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 <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 オペレーティング環境版)

/*
 * @(#)jawt_md.h        1.2 99/05/27
 *
 * Copyright (c) 1999 Oracle and/or its affiliates.  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 (Microsoft Windows 版)

/*
 * @(#)jawt_md.h        1.2 99/05/27
 *
 * Copyright (c) 1999 Oracle and/or its affiliates.  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

/*
 * 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 © 1993, 2013, Oracle and/or its affiliates. All rights reserved.