Java AWT Native Interface Specification and Guide

介绍

Java AWT本地接口(JAWT)包括一小组本地(例如基于C语言的)API,提供了一种标准支持的方式,用于Java API窗口和表面与平台本地API窗口和表面之间的交互。非Java库可以渲染到Java拥有的窗口。

注意:在本文档中,“Java AWT本地接口”、“AWT本地接口”和“JAWT”这些术语是可互换的,指的是同一规范。

没有JAWT的情况下进行本地渲染的根本障碍在于渲染代码无法确定绘制位置。本地代码需要访问有关Java绘图表面的信息(例如Canvas的底层本地ID的句柄),但无法获取。

没有该信息(即没有JAWT),应用程序只能通过创建自己的与Java完全不共享的顶级窗口来使用本地渲染。这对大多数用途来说是不可接受的。除了通过JAWT使用外,这被认为是完全内部的Java平台实现:私有的、不受支持的和未记录的。

在技术上可能的情况下,JAWT应该在所有支持头部实现中得到支持,尽管这不受JCK的强制执行。API有一个特定于平台和一个独立于平台的部分,以考虑每个平台的不同数据结构和要求。本文档指定了独立于平台的部分,并记录了Oracle JDK支持的桌面操作环境的平台相关部分。对于AWT,平台这个术语与底层操作系统的关系不如与桌面窗口环境的关系紧密。

使用AWT本地接口的原因包括

缺点包括

附录中的头文件jawt.h完全指定了JAWT提供的API。

本文档稍后将提供并讨论使用AWT本地接口的示例,展示了其使用的简便性。

JAWT的使用取决于JNI

Java标准版的定义包括JNI,即Java本机接口。许多Java开发人员永远不需要使用它,但该接口是Java语言程序直接与为主机处理器架构编译的应用程序代码进行交互的唯一标准支持方式。无论何时需要混合语言,都会使用JNI。这些情况绝不仅限于像AWT这样的情况。例如,您可以使用JNI与与系统通过USB端口连接的外围设备(如扫描仪)通信的本地代码集成。

因此,JNI足够通用,可用于访问几乎任何类型的本地库。本文档的其余部分假定您熟悉如何使用JNI。

如何使用JAWT

在本节中,我们描述了AWT本地接口的最常见用法--覆盖paint方法,将绘图操作定向到本地渲染库,然后查询Java虚拟机以确定其渲染所需的信息。但是,请注意,任何本地代码都可以使用AWT本地接口来了解目标绘图表面,而不仅仅是paint方法中的代码。

将本地渲染库连接到Java Canvas的第一步是定义一个扩展Canvas并覆盖paint方法的新类。Java系统通过paint方法路由所有Canvas对象的绘图操作,就像对所有其他GUI对象一样。Canvas作为渲染表面是一个不错的选择,因为它不像Button那样具有任何内容。

要在本地渲染库中实现的新paint方法必须声明为public native void,并且本地库本身是通过在类的static块中包含对System.loadLibrary("myRenderingLib")的调用来在运行时加载的。myRenderingLib名称用于本地共享库;对于Linux,在磁盘上的库文件的实际名称为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类并生成一个描述Java期望使用的本地paint方法接口的C/C++头文件:

javac MyCanvas.java -h outputdir

最后一步---也是最有趣的一步---是编写本地渲染方法,其接口符合javac -h生成的头文件,并将其构建为标准共享库(在上面的示例中称为myRenderingLib),通过链接到适用于目标平台的JDK提供的$JDK_HOME/lib/$JAWT_LIB库。其中$JAWT_LIB具有基本名称jawt,并遵循平台共享对象命名规则。即:

此代码将回调Java虚拟机以获取其需要访问MyCanvas对等体的绘图表面信息。一旦此信息可用,代码就可以使用底层操作系统提供的标准绘图例程直接绘制到MyCanvas

以下是针对基于X11的绘图环境(Linux)和存在AWT本地接口的Java虚拟机的本地paint方法的示例源代码:

#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_9;
    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,它在jawt.h(由jawt_md.h包含)中定义;它提供了本地代码需要完成工作的所有信息。本地方法的第一部分是样板代码:它填充JAWT结构,获取JAWT_DrawingSurface结构,锁定表面(一次只能有一个绘图引擎!),然后获取包含指向必要平台特定绘图信息的指针(在platformInfo字段中)的JAWT_DrawingSurfaceInfo结构。它还包括绘图表面的边界矩形和当前剪切区域。

platformInfo指向的信息的结构在一个名为jawt_md.h的机器相关头文件中定义。对于X11绘图,它包括有关与MyCanvas关联的X11显示和X11可绘制的信息。绘图操作完成后,还有更多的样板代码,因为JAWT_DrawingSurfaceInfo被释放,JAWT_DrawingSurface被解锁并释放。

对于Microsoft Windows平台上的GDI API的相应代码结构类似,但将包括用于Microsoft Windows的jawt_md.h版本以及位于绘图表面信息的platformInfo字段中的结构将被强制转换为JAWT_Win32DrawingSurfaceInfo*。当然,实际的绘图操作需要更改为适用于Microsoft Windows平台的操作。MacOS也是如此。

总结

直接在Java Canvas中从本地代码库绘制是对计划将传统软件系统迁移到Java的开发人员非常有用,特别是包括高性能渲染引擎的系统。这样可以更轻松地分阶段迁移,保留对性能敏感的渲染代码不变,同时将其他不太敏感的代码部分转换为Java。结果可能是一个现代的以Java为中心的应用程序,提供了可移植性和开发效率的好处,但不会牺牲对关键本地代码性能的投资。

参考

Java本机接口的权威参考是Sheng Liang撰写的《Java本机接口:程序员指南和规范》。该书于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本地接口。
 *
 * AWT本地接口允许本地C或C++应用程序访问AWT中的本地结构。这是为了便于将传统的C和C++应用程序移植到Java,并满足需要自行对画布进行本地渲染以提高性能或其他原因的开发人员的需求。
 *
 * 相反,它还为已经具有本地窗口的应用程序提供机制,以便将其提供给AWT进行AWT渲染。
 *
 * 由于每个平台的本地数据结构和窗口系统的API可能不同,因此应用程序必须提供每个平台的源代码并编译和交付每个平台的本地代码以使用此API。
 *
 * 这些接口不是Java SE规范的一部分,VM不需要实现此API。但强烈建议所有支持headful AWT的实现也支持这些接口。
 *
 */

/*
 * AWT本地绘图表面(JAWT_DrawingSurface)。
 *
 * 对于每个平台,都有一个本地绘图表面结构。此特定于平台的结构可以在jawt_md.h中找到。建议其他平台遵循相同的模型。还建议所有平台上的VM支持jawt_md.h中的现有结构。
 *
 *******************
 * 使用示例:
 *******************
 *
 * 在Microsoft Windows上,程序员希望访问画布的HWND以执行本地渲染。程序员已将其画布子类的paint()方法声明为本地:
 *
 *
 * 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;
 *
 *     // 获取AWT。请求版本9以访问该版本中的功能。
 *     awt.version = JAWT_VERSION_9;
 *     result = JAWT_GetAWT(env, &awt);
 *     assert(result != JNI_FALSE);
 *
 *     // 获取绘图表面
 *     ds = awt.GetDrawingSurface(env, canvas);
 *     assert(ds != NULL);
 *
 *     // 锁定绘图表面
 *     lock = ds->Lock(ds);
 *     assert((lock & JAWT_LOCK_ERROR) == 0);
 *
 *     // 获取绘图表面信息
 *     dsi = ds->GetDrawingSurfaceInfo(ds);
 *
 *     // 获取特定于平台的绘图信息
 *     dsi_win = (JAWT_Win32DrawingSurfaceInfo*)dsi->platformInfo;
 *
 *     //////////////////////////////
 *     // !!! 在此处进行绘制 !!! //
 *     //////////////////////////////
 *
 *     // 释放绘图表面信息
 *     ds->FreeDrawingSurfaceInfo(dsi);
 *
 *     // 解锁绘图表面
 *     ds->Unlock(ds);
 *
 *     // 释放绘图表面
 *     awt.FreeDrawingSurface(ds);
 * }
 *
 */

/*
 * JAWT_Rectangle
 * 用于本地矩形的结构。
 */
typedef struct jawt_Rectangle {
    jint x;
    jint y;
    jint width;
    jint height;
} JAWT_Rectangle;

struct jawt_DrawingSurface;

/*
 * JAWT_DrawingSurfaceInfo
 * 用于包含组件的底层绘图信息的结构。
 */
typedef struct jawt_DrawingSurfaceInfo {
    /*
     * 指向特定于平台的信息的指针。可以安全地将其转换为Microsoft Windows上的JAWT_Win32DrawingSurfaceInfo或Linux上的JAWT_X11DrawingSurfaceInfo。在MacOS上,这是一个符合JAWT_SurfaceLayers协议的NSObject的指针。
     * 有关详细信息,请参阅jawt_md.h。
     */
    void* platformInfo;
    /* 底层绘图表面的缓存指针 */
    struct jawt_DrawingSurface* ds;
    /* 绘图表面的边界矩形 */
    JAWT_Rectangle bounds;
    /* 剪辑中的矩形数 */
    jint clipSize;
    /* 剪辑矩形数组 */
    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
 * 用于包含组件的底层绘图信息的结构。
 * 必须从调用GetDrawingSurface的同一线程执行JAWT_DrawingSurface上的所有操作。
 */
typedef struct jawt_DrawingSurface {
    /* 调用线程的Java环境的缓存引用。
     * 如果从不同线程调用Lock()、Unlock()、GetDrawingSurfaceInfo()或FreeDrawingSurfaceInfo(),则在调用这些函数之前应设置此数据成员。
     */
    JNIEnv* env;
    /* 目标对象的缓存引用 */
    jobject target;
    /*
     * 锁定目标组件的表面以进行本地渲染。
     * 绘制完成后,必须使用Unlock()解锁表面。此函数返回一个位掩码,其中包含以下一个或多个值:
     *
     * JAWT_LOCK_ERROR - 发生错误且无法锁定表面时。
     *
     * JAWT_LOCK_CLIP_CHANGED - 剪辑区域已更改时。
     *
     * JAWT_LOCK_BOUNDS_CHANGED - 表面边界已更改时。
     *
     * JAWT_LOCK_SURFACE_CHANGED - 表面本身已更改时。
     */
    jint (JNICALL *Lock)
        (struct jawt_DrawingSurface* ds);
    /*
     * 获取绘图表面信息。
     * 返回的值可能已缓存,但如果进行了额外的Lock()或Unlock()调用,则值可能会更改。
     * 必须在此之前调用Lock()才能返回有效值。
     * 如果发生错误,则返回NULL。
     * 使用返回的值后,必须调用FreeDrawingSurfaceInfo。
     */
    JAWT_DrawingSurfaceInfo* (JNICALL *GetDrawingSurfaceInfo)
        (struct jawt_DrawingSurface* ds);
    /*
     * 释放绘图表面信息。
     */
    void (JNICALL *FreeDrawingSurfaceInfo)
        (JAWT_DrawingSurfaceInfo* dsi);
    /*
     * 解锁目标组件的绘图表面以进行本地渲染。
     */
    void (JNICALL *Unlock)
        (struct jawt_DrawingSurface* ds);
} JAWT_DrawingSurface;

/*
 * JAWT
 * 用于包含本地AWT函数的结构。
 */
typedef struct jawt {
    /*
     * 此结构的版本。必须在调用JAWT_GetAWT()之前始终设置它。它会影响返回的函数。
     * 必须是已知的预定义版本之一。
     */
    jint version;
    /*
     * 从目标jobject返回绘图表面。此值可能已缓存。
     * 如果发生错误,则返回NULL。
     * 目标必须是java.awt.Component(应为Canvas或Window以进行本地渲染)。
     * 使用返回的JAWT_DrawingSurface后,必须调用FreeDrawingSurface()。
     */
    JAWT_DrawingSurface* (JNICALL *GetDrawingSurface)
        (JNIEnv* env, jobject target);
    /*
     * 释放在GetDrawingSurface中分配的绘图表面。
     */
    void (JNICALL *FreeDrawingSurface)
        (JAWT_DrawingSurface* ds);
    /*
     * 自1.4版以来
     * 用于同步目的锁定整个AWT
     */
    void (JNICALL *Lock)(JNIEnv* env);
    /*
     * 自1.4版以来
     * 用于同步目的解锁整个AWT
     */
    void (JNICALL *Unlock)(JNIEnv* env);
    /*
     * 自1.4版以来
     * 从本地平台句柄返回对java.awt.Component的引用。在Windows上,这对应于HWND;在Linux上,这是一个Drawable。对于其他平台,请参阅适当的机器相关头文件以获取描述。
     * 此函数返回的引用是仅在此环境中有效的本地引用。
     * 如果找不到具有匹配平台信息的组件,则此函数将返回一个NULL引用。
     */
    jobject (JNICALL *GetComponent)(JNIEnv* env, void* platformInfo);

    /**
     * 自9版以来
     * 创建放置在本地容器中的java.awt.Frame。容器由本地平台句柄引用。例如,在Windows上,这对应于HWND。对于其他平台,请参阅适当的机器相关头文件以获取描述。此函数返回的引用是仅在此环境中有效的本地引用。
     * 如果找不到具有匹配平台信息的框架,则此函数将返回一个NULL引用。
     */
    jobject (JNICALL *CreateEmbeddedFrame) (JNIEnv *env, void* platformInfo);

    /**
     * 自9版以来
     * 移动并调整嵌入式框架。左上角的新位置由相对于本地父组件的x和y参数指定。新大小由宽度和高度指定。
     *
     * 嵌入式框架应由CreateEmbeddedFrame()方法创建,否则此函数将不起作用。
     *
     * java.awt.Component.setLocation()和java.awt.Component.setBounds()对于EmbeddedFrame实际上不会在本地父组件内移动它。这些方法始终将嵌入式框架定位在(0, 0)以保持向后兼容性。为了允许移动嵌入式框架,引入了此方法,并且它的工作方式与通常的非嵌入式组件的setLocation()和setBounds()相同。
     *
     * 不建议将通常的get/setLocation()和get/setBounds()与此新方法一起使用。
     */
    void (JNICALL *SetBounds) (JNIEnv *env, jobject embeddedFrame,
            jint x, jint y, jint w, jint h);
    /**
     * 自9版以来
     * 合成本地消息以激活或停用EmbeddedFrame窗口,具体取决于参数doActivate的值,如果为“true”则激活窗口;否则,停用窗口。
     *
     * 嵌入式框架应由CreateEmbeddedFrame()方法创建,否则此函数将不起作用。
     */
    void (JNICALL *SynthesizeWindowActivation) (JNIEnv *env,
            jobject embeddedFrame, jboolean doActivate);
} JAWT;

/*
 * 获取AWT本地结构。如果发生错误,则此函数返回JNI_FALSE。
 */
_JNI_IMPORT_OR_EXPORT_
jboolean JNICALL JAWT_GetAWT(JNIEnv* env, JAWT* awt);

/*
 * 将JAWT.version指定为以下常量之一
 * 指定较早版本将限制可用函数为该较早版本的JAWT提供的函数。
 * 请参见每个API上的“自”注释。没有“自”注释的方法可以假定存在于JAWT_VERSION_1_3中。
 */
#define JAWT_VERSION_1_3 0x00010003
#define JAWT_VERSION_1_4 0x00010004
#define JAWT_VERSION_1_7 0x00010007
#define JAWT_VERSION_9 0x00090000


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

#endif /* !_JAVASOFT_JAWT_H_ */

jawt_md.h(Linux/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

/*
 * AWT本地接口的X11特定声明。
 * 有关使用示例,请参阅jawt.h中的说明。
 */
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

/*
 * AWT本地接口的Microsoft Windows特定声明。
 * 请参阅jawt.h中的注释以获取使用示例。
 */
typedef struct jawt_Win32DrawingSurfaceInfo {
    /* 本地窗口、DDB或DIB句柄 */
    union {
        HWND hwnd;
        HBITMAP hbitmap;
        void* pbits;
    };
    /*
     * 应始终使用此HDC,而不是从BeginPaint()返回的HDC或任何调用GetDC()返回的HDC。
     */
    HDC hdc;
    HPALETTE hpalette;
} JAWT_Win32DrawingSurfaceInfo;

#ifdef __cplusplus
}
#endif

#endif /* !_JAVASOFT_JAWT_MD_H_ */

jawt_md.h (MacOS 版本)

#ifndef _JAVASOFT_JAWT_MD_H_
#define _JAVASOFT_JAWT_MD_H_

#include "jawt.h"

#ifdef __OBJC__
#import <QuartzCore/CALayer.h>
#endif

#ifdef __cplusplus
extern "C" {
#endif

/*
 * AWT本地接口的MacOS特定声明。
 * 请参阅jawt.h中的注释以获取使用示例。
 */

/*
 * 当使用低于1.7版本的JAWT_GetAWT调用时,必须传递此标志,否则将无法获得有效的绘图表面,
 * 并且JAWT_GetAWT将返回false。这是为了与使用Java 6的应用程序保持兼容,该版本具有多个渲染模型。
 * 当使用1.7或更高版本的JAWT时,不需要此标志,因为这是唯一支持的渲染模式。
 *
 * 示例:
 *   JAWT awt;
 *   awt.version = JAWT_VERSION_1_4 | JAWT_MACOSX_USE_CALAYER;
 *   jboolean success = JAWT_GetAWT(env, &awt);
 */
#define JAWT_MACOSX_USE_CALAYER 0x80000000

/*
 * 当使用本机Cocoa工具包时,存储在JAWT_DrawingSurfaceInfo->platformInfo中的指针指向符合
 * JAWT_SurfaceLayers协议的NSObject。设置此对象的layer属性将导致指定的图层叠加在组件的矩形上。
 * 如果组件所属的窗口附加有CALayer,则可以通过windowLayer属性访问此图层。
 */
#ifdef __OBJC__
@protocol JAWT_SurfaceLayers
@property (readwrite, retain) CALayer *layer;
@property (readonly) CALayer *windowLayer;
@end
#endif

#ifdef __cplusplus
}
#endif

#endif /* !_JAVASOFT_JAWT_MD_H_ */