JDK1.3 中的本地绘制支持

  在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 Files\Microsoft Visual Studio\VC98
  SET JDK13=D:\JDK1.3
  好了,一切准备就绪。在运行该样例之前,请确保MyWindow.DLL、\JDK1.3\BIN及\JDK1.3\JRE\BIN都在PATH内,还要保证当前目录在CLASSPATH中;这将确保MyWindow.class会被成功加载。在确信PATH和CLASSPATH都设置妥当后,在命令行输入java MyWindow来运行此应用程序。为方便您的使用,window.zip中包含了一个批处理文件RUN.BAT(请参阅参考资源)。要为JDK 1.3设置PATH和CLASSPATH,请编辑RUN.BAT。
  构建环境
  头文件:在JDK的include目录中新增了专用于Windows的C头文件。它们是:
  include/jawt.h.
  include/win32/jawt_md.h.
  依据JavaSoft网站的说明,这些头文件并不是Java 2平台正式规范的组成部分;提供这些头文件只是为希望用一种标准化方法访问本地绘图功能的开发人员提供一种便利。我认为这表示将JDK移植到其它平台的厂商可以不提供这个API。
  库:一个以jawt.lib命名的新库已添加到SDK的库目录中。如前所述,这个库包含一个用于把J2AWT包含到您的应用程序中所需要的入口点。例如,要链接到GetDrawingSurfaceInfo入口点,您需要在您的程序中包含jawt.lib。
  工具:javah工具用来为Java类的本地函数生成C/C++头文件,javac工具用来编译Java源文件。
  小结
  将原有软件系统移植到Java中并不容易,尤其是当原有软件包含高性能的绘图器时。Java 2 AWT本地接口使得分阶段移植变得较为容易,它允许您首先移植对性能要求不高的代码,然后再移植关键的绘制代码。它同时使第三方窗口小部件开发厂商更能严肃地看待针对Java产品的开发。有了Java 2 AWT本地应用程序接口,您就可以移植原有的GUI代码,并更快地完成开发,这样就不会牺牲您为提高本地代码关键部分的性能而作的投资。