在 JDK1.3 出现以前,您仅能将 Java 本地接口用于非用户界面的工作。JDK 1.3 引入了新的 Java 2 AWT 本地接口,这使您可以在 Java 程序中使用非 Java 的 GUI 组件,尽管这样做会失去纯 Java 解决方案的可移植性。在使用 J2AWT 时,您必须针对要使用它的每个平台制作本地动态连接库或共享库。
下面这段话摘自 JDK1.3 的某个头文件,它说明了这种新的 API 的开发背景及原因:
AWT 支持使用本地 C 或 C++ 应用程序访问 AWT 的本地结构。这是为了便于将原有的 C 或 C++ 应用程序移植到 Java 并满足需要 ... [这些应用程序] 出于性能方面的原因在画布上自行进行本地绘制
在 JDK1.3 以前,Java 编程没有明确的方法来访问基层的同等 GUI 组件的句柄。在 JDK 1.3 中, Sun 公司创建了一种标准机制,通过这种机制,开发人员可以使本地 GUI 应用程序和库在 Java AWT Canvas 对象中进行绘制。这意味着现在有一种正式的、有保证的方法来获得支持这一功能的信息。当 JDK 1.3 与其他操作平台对接时,所有的接口都提供相同的信息 -- 而不管使用的是什么系统。JDK 1.3 的 Windows 版本和 Solaris 版本是首先提供这种支持的实现。
Sun 公司引入这一功能组件有几方面的原因。首先, JDK 1.3 使得人们可以将依赖第三方产品的复杂原有软件移植到 Java 上,而不必等到第三方产品本身完成移植以后。第二个原因即性能;如果本地的 GUI 代码经过人们长时期的努力得到优化,则原样保留这些软件具有重要的商业价值。
在本文中,我将介绍一些该功能部件的基本概念。我将逐步开发一个窗口小部件样例,该窗口小部件使用Win32 API 进行绘制。下图是最终的窗口小部件的快照,一个带有笑脸的圆形窗口。
运行中的窗口小部件 |
分步概览 第一步,定义一个 Java 类 -- 比如说,Mywindow -- 使其继承 Canvas 类并重载 paint 方法。您使用 paint 方法执行 AWT 对象的绘制操作,并在覆盖该方法时加上 native 关键字。覆盖方法使您能够使用自己的本地代码。您必须构建自己的本地代码并把它编译成一个动态连接库,就像我们处理其他的 Java 本地接口应用程序一样,在本例中,我们将调用 MyWindow.DLL 库。在 Solaris 和 Linux 上则为共享对象或共享库。您还需要用 System.loadLibrary("MyWindow") 调用将 MyWindow.DLL 库加载到您的名为 MyWindow 的 Java 类中。
完成这一示例需要二个部分:其一是 MyWindow.Java ,它提供 Canvas 类的子类,其二是 MyWindow.CPP ,它包含基于 Java 本地接口的绘制子程序的入口点。 在参考资源部分可找到 MyWindow.Java 、MyWindow.CPP 及自动执行编译的批处理文件 BUILD.BAT 。
第一步: 创建 MyWindow Java 类 J2AWT 用于这种方法时有一个主要的局限性:本地代码只能对 java.awt.Canvas 类的子类进行操作。这正是 MyWindow 继承 Canvas 类的原因。在 Java 应用程序中,您可以像使用 Canvas 的其它子类那样使用 MyWindow ;在本例中,我将 MyWindow 添加到 Jwindow 中。
import java.awt.*; import javax.swing.*;
public class MyWindow extends Canvas { static { //加载包含 paint 代码的库。 System.loadLibrary("MyWindow"); }
//绘制操作的本地入口点 public native void paint(Graphics g);
public static void main( String[] argv ){ Frame f = new Frame(); f.setSize(300,400);
JWindow w = new JWindow(f); w.setBackground(new Color(0,0,0,255)); w.getContentPane().setBackground(new Color(0,0,0,255)); w.getContentPane().add(new MyWindow()); w.setBounds(300,300,300,300); w.setVisible(true); } }
请注意:您是在静态块中加载 MyWindow.DLL 。这正是 Java 应用程序访问本地代码的方式。(我稍候就会开发这段本地代码。)同时还应注意:paint 方法是用 native 关键字声明的,并且没有提供任何实现;这样做是为了让虚拟机知道,应该从在静态块中加载的动态连接库中调用该本地方法。
第二步:生成该类的 JNI 头文件 要为以上定义的类生成 Java 本地接口头文件,需使用 javah MyWindow.class 命令。首先应确保这个类文件在您的 CLASSPATH 中。以下是所生成的 MyWindow.h 的一部分,给出了函数声明。
/* * Class: MyWindow * Method: paint * Signature: (Ljava/awt/Graphics;)V */ JNIEXPORT void JNICALL Java_MyWindow_paint (JNIEnv *, jobject, jobject);
第三步:开发完整的 MyWindow.CPP 以下是完整的 MyWindow.CPP ,其中包含 MyWindow.Java 中所需要的绘图程序的本地代码。
#include <windows.h> #include <assert.h> #include "jawt_md.h" #include "MyWindow.h"
#define X(x) (int)(xLeft + (x)*xScale/100) // 缩放宏 #define Y(y) (int)(yTop + (y)*yScale/100) // 以使尺度在 0-100 之间 #define CX(x) (int)((x)*xScale/100) #define CY(y) (int)((y)*yScale/100)
void DrawSmiley(HWND hWnd, HDC hdc); HRGN hrgn = NULL;
JNIEXPORT void JNICALL Java_MyWindow_paint(JNIEnv* env, jobject canvas, jobject graphics) { JAWT awt; JAWT_DrawingSurface* ds; JAWT_DrawingSurfaceInfo* dsi; JAWT_Win32DrawingSurfaceInfo* dsi_win; jboolean result; jint lock;
// 获取 AWT awt.version = JAWT_VERSION_1_3; result = JAWT_GetAWT(env, &awt); assert(result != JNI_FALSE); // 获取绘图界面 ds = awt.GetDrawingSurface(env, canvas); if(ds == NULL) return; // 锁定绘图表面 lock = ds->Lock(ds); assert((lock & JAWT_LOCK_ERROR) == 0);
// 获取绘图表面的信息 dsi = ds->GetDrawingSurfaceInfo(ds);
// 获取特定平台的绘图信息 dsi_win = (JAWT_Win32DrawingSurfaceInfo*)dsi->platformInfo;
HDC hdc = dsi_win->hdc; HWND hWnd = dsi_win->hwnd; ////////////////////////////// // !!! 在此处进行绘图 !!! // ////////////////////////////// if(hrgn == NULL) { RECT rcBounds; GetWindowRect(hWnd,&rcBounds); long xLeft = 0; // 用于缩放宏 long yTop = 0; long xScale = rcBounds.right-rcBounds.left; long yScale = rcBounds.bottom-rcBounds.top; hrgn = CreateEllipticRgn(X(10), Y(15), X(90), Y(95)); SetWindowRgn(GetParent(hWnd),hrgn,TRUE); InvalidateRect(hWnd,NULL,TRUE); } else { DrawSmiley(hWnd,hdc); } // 释放绘图表面的信息 ds->FreeDrawingSurfaceInfo(dsi); // 为绘图表面解锁 ds->Unlock(ds); // 释放绘图表面 awt.FreeDrawingSurface(ds); }
void DrawSmiley(HWND hWnd, HDC hdc) { RECT rcBounds; GetWindowRect(hWnd,&rcBounds); long xLeft = 0; // 用于缩放宏 long yTop = 0; long xScale = rcBounds.right-rcBounds.left; long yScale = rcBounds.bottom-rcBounds.top;
// 基于控制大小的画笔宽度 int iPenWidth = max(CX(5), CY(5)); HBRUSH brushBlack; HBRUSH brushYellow; HPEN penBlack = CreatePen(PS_SOLID, iPenWidth, RGB(0x00,0x00,0x00)); // 用于绘制填充椭圆的空画笔 HPEN penNull = CreatePen(PS_NULL, 0, (COLORREF)0);
brushBlack = CreateSolidBrush(RGB(0x00,0x00,0x00)); brushYellow = CreateSolidBrush(RGB(0xff,0xff,0x00));
HPEN pPenSave = (HPEN)SelectObject(hdc, penBlack); HBRUSH pBrushSave = (HBRUSH)SelectObject(hdc,brushYellow); Ellipse(hdc,X(10), Y(15), X(90), Y(95)); // 头部
Arc(hdc,X(25), Y(10), X(75), Y(80), // 嘴部(微笑) X(35), Y(70), X(65), Y(70));
SelectObject(hdc,&penNull); // 无绘图宽度 SelectObject(hdc,&brushBlack);
Ellipse(hdc,X(57), Y(35), X(65), Y(50)); Ellipse(hdc,X(35), Y(35), X(43), Y(50)); // 右眼 Ellipse(hdc,X(46), Y(50), X(54), Y(65)); // 鼻子
SetBkMode(hdc,TRANSPARENT); // 使用前景颜色
SelectObject(hdc,pBrushSave); SelectObject(hdc,pPenSave); }
这里的关键数据结构是 JAWT ,它是在 jawt.h 中定义的(通过 jawt_md.h 包含在内)。它使程序可以访问本地代码在基于 Java 的 GUI 组件上绘图所需的所有信息。本地方法的第一部分是套式:置入 JAWT 结构,获得一个 JAWT_Win32DrawingSurfaceInfo 结构,锁定表面(请一次只使用一种绘图工具!),然后,获取一个 JAWT_DrawingSurfaceInfo 结构,该结构包含特定平台下绘图所必需的指针(在 platformInfo 字段中)。它也包含绘图界面的矩形界限框及当前剪切区域。有关详细信息,请查看 jawt.h 和 jawt_md.h (请参阅下面标题为 “构建环境”的部分)。
Java_MyWindow_paint 是一个入口点,JVM 通过调用它来绘制 MyWindow 。辅助函数 DrawSmiley 使用 Win32 调用来完成实际的绘制工作。要在您的应用程序中包含 GetDrawingSurfaceInfo ,请使用外部库 jawt.lib (请参阅 “构建环境”)。
第四步:编辑 BUILD.BAT 在运行 BUILD.BAT 之前首先对它进行编辑,并像如下所示的那样,为您的 Visual C++ 及 JDK1.3 设置路径。BUILD.BAT 对 MyWindow.java 进行编译,生成 MyWindow.h ,然后将 MyWindow.CPP 编译为 MyWindow.DLL 。
SET DEVSTUDIO=D:Program FilesMicrosoft Visual StudioVC98 SET JDK13=D:JDK1.3
好了,一切准备就绪。在运行该样例之前,请确保 MyWindow.DLL 、JDK1.3BIN 及 JDK1.3JREBIN 都在 PATH 内,还要保证当前目录在 CLASSPATH 中;这将确保 MyWindow.class 会被成功加载。在确信 PATH 和 CLASSPATH 都设置妥当后,在命令行输入 java MyWindow 来运行此应用程序。为方便您的使用,window.zip 中包含了一个批处理文件 RUN.BAT (请参阅参考资源)。要为 JDK 1.3 设置PATH 和 CLASSPATH ,请编辑 RUN.BAT 。
构建环境
小结 将原有软件系统移植到 Java 中并不容易,尤其是当原有软件包含高性能的绘图器时。Java 2 AWT 本地接口使得分阶段移植变得较为容易,它允许您首先移植对性能要求不高的代码,然后再移植关键的绘制代码。它同时使第三方窗口小部件开发厂商更能严肃地看待针对 Java 产品的开发。有了 Java 2 AWT 本地应用程序接口,您就可以移植原有的 GUI 代码,并更快地完成开发,这样就不会牺牲您为提高本地代码关键部分的性能而作的投资。
|