JDK1.3中使用非Java的GUI組件
如果您正在使用 JDK1.3,則您可以借助于 Java 2 AWT 本地接口,使用 C 或 C++ 方法來繪制基于 AWT 的 Canvas 對象。
因此,本地 GUI 組件可以嵌入到 Java 應(yīng)用程序中;同時,正像 JDK1.3 以前的其他版本一樣,您可以使用 Java 本地接口從 Java 應(yīng)用程序中調(diào)用本地方法。Davanum Srinivas 解釋了如何在 Java 中使用現(xiàn)有的本地用戶界面庫。他還提供了 Win32 操作系統(tǒng)下的一個具體示例。
在 JDK1.3 出現(xiàn)以前,您僅能將 Java 本地接口用于非用戶界面的工作。JDK 1.3 引入了新的 Java 2 AWT 本地接口,這使您可以在 Java 程序中使用非 Java 的 GUI 組件,盡管這樣做會失去純 Java 解決方案的可移植性。在使用 J2AWT 時,您必須針對要使用它的每個平臺制作本地動態(tài)連接庫或共享庫。
下面這段話摘自 JDK1.3 的某個頭文件,它說明了這種新的 API 的開發(fā)背景及原因:
AWT 支持使用本地 C 或 C++ 應(yīng)用程序訪問 AWT 的本地結(jié)構(gòu)。這是為了便于將原有的 C 或 C++ 應(yīng)用程序移植到 Java 并滿足需要 ... [這些應(yīng)用程序] 出于性能方面的原因在畫布上自行進行本地繪制
在 JDK1.3 以前,Java 編程沒有明確的方法來訪問基層的同等 GUI 組件的句柄。在 JDK 1.3 中, Sun 公司創(chuàng)建了一種標準機制,通過這種機制,開發(fā)人員可以使本地 GUI 應(yīng)用程序和庫在 Java AWT Canvas 對象中進行繪制。這意味著現(xiàn)在有一種正式的、有保證的方法來獲得支持這一功能的信息。當 JDK 1.3 與其他操作平臺對接時,所有的接口都提供相同的信息 -- 而不管使用的是什么系統(tǒng)。JDK 1.3 的 Windows 版本和 Solaris 版本是首先提供這種支持的實現(xiàn)。
Sun 公司引入這一功能組件有幾方面的原因。首先, JDK 1.3 使得人們可以將依賴第三方產(chǎn)品的復(fù)雜原有軟件移植到 Java 上,而不必等到第三方產(chǎn)品本身完成移植以后。第二個原因即性能;如果本地的 GUI 代碼經(jīng)過人們長時期的努力得到優(yōu)化,則原樣保留這些軟件具有重要的商業(yè)價值。
在本文中,我將介紹一些該功能部件的基本概念。我將逐步開發(fā)一個窗口小部件樣例,該窗口小部件使用Win32 API 進行繪制。下圖是最終的窗口小部件的快照,一個帶有笑臉的圓形窗口。
運行中的窗口小部件
分步概覽
第一步,定義一個 Java 類 -- 比如說,Mywindow -- 使其繼承 Canvas 類并重載 paint 方法。您使用 paint 方法執(zhí)行 AWT 對象的繪制操作,并在覆蓋該方法時加上 native 關(guān)鍵字。覆蓋方法使您能夠使用自己的本地代碼。您必須構(gòu)建自己的本地代碼并把它編譯成一個動態(tài)連接庫,就像我們處理其他的 Java 本地接口應(yīng)用程序一樣,在本例中,我們將調(diào)用 MyWindow.DLL 庫。在 Solaris 和 Linux 上則為共享對象或共享庫。您還需要用 System.loadLibrary("MyWindow") 調(diào)用將 MyWindow.DLL 庫加載到您的名為 MyWindow 的 Java 類中。
完成這一示例需要二個部分:其一是 MyWindow.Java ,它提供 Canvas 類的子類,其二是 MyWindow.CPP ,它包含基于 Java 本地接口的繪制子程序的入口點。 在參考資源部分可找到 MyWindow.Java、MyWindow.CPP 及自動執(zhí)行編譯的批處理文件 BUILD.BAT。
第一步: 創(chuàng)建 MyWindow Java 類
J2AWT 用于這種方法時有一個主要的局限性:本地代碼只能對 java.awt.Canvas 類的子類進行操作。這正是 MyWindow 繼承 Canvas 類的原因。在 Java 應(yīng)用程序中,您可以像使用 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);
- }
- }
請注意:您是在靜態(tài)塊中加載 MyWindow.DLL。這正是 Java 應(yīng)用程序訪問本地代碼的方式。(我稍候就會開發(fā)這段本地代碼。)同時還應(yīng)注意:paint 方法是用 native 關(guān)鍵字聲明的,并且沒有提供任何實現(xiàn);這樣做是為了讓虛擬機知道,應(yīng)該從在靜態(tài)塊中加載的動態(tài)連接庫中調(diào)用該本地方法。
第二步:生成該類的 JNI 頭文件
要為以上定義的類生成 Java 本地接口頭文件,需使用 javah MyWindow.class 命令。首先應(yīng)確保這個類文件在您的 CLASSPATH 中。以下是所生成的 MyWindow.h 的一部分,給出了函數(shù)聲明。
- /*
- * Class: MyWindow
- * Method: paint
- * Signature: (Ljava/awt/Graphics;)V
- */
- JNIEXPORT void JNICALL Java_MyWindow_paint
- (JNIEnv *, jobject, jobject);
第三步:開發(fā)完整的 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);
- }
這里的關(guān)鍵數(shù)據(jù)結(jié)構(gòu)是 JAWT,它是在 jawt.h 中定義的(通過 jawt_md.h 包含在內(nèi))。它使程序可以訪問本地代碼在基于 Java 的 GUI 組件上繪圖所需的所有信息。本地方法的第一部分是套式:置入 JAWT 結(jié)構(gòu),獲得一個 JAWT_Win32DrawingSurfaceInfo 結(jié)構(gòu),鎖定表面(請一次只使用一種繪圖工具?。?然后,獲取一個 JAWT_DrawingSurfaceInfo 結(jié)構(gòu),該結(jié)構(gòu)包含特定平臺下繪圖所必需的指針(在 platformInfo字段中)。它也包含繪圖界面的矩形界限框及當前剪切區(qū)域。有關(guān)詳細信息,請查看 jawt.h 和 jawt_md.h (請參閱下面標題為 “構(gòu)建環(huán)境”的部分)。
Java_MyWindow_paint 是一個入口點,JVM 通過調(diào)用它來繪制 MyWindow。輔助函數(shù) DrawSmiley 使用 Win32 調(diào)用來完成實際的繪制工作。要在您的應(yīng)用程序中包含 GetDrawingSurfaceInfo,請使用外部庫 jawt.lib(請參閱 “構(gòu)建環(huán)境”)。
第四步:編輯 BUILD.BAT
在運行 BUILD.BAT 之前首先對它進行編輯,并像如下所示的那樣,為您的 Visual C++ 及 JDK1.3 設(shè)置路徑。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 內(nèi),還要保證當前目錄在 CLASSPATH 中;這將確保 MyWindow.class 會被成功加載。在確信 PATH 和 CLASSPATH 都設(shè)置妥當后,在命令行輸入 java MyWindow 來運行此應(yīng)用程序。為方便您的使用,window.zip 中包含了一個批處理文件 RUN.BAT(請參閱參考資源)。要為 JDK 1.3 設(shè)置PATH 和 CLASSPATH,請編輯 RUN.BAT。
構(gòu)建環(huán)境
頭文件:在 JDK 的 include 目錄中新增了專用于 Windows 的 C 頭文件。它們是:
include/jawt.h.
include/win32/jawt_md.h.
依據(jù) JavaSoft 網(wǎng)站的說明,這些頭文件并不是 Java 2 平臺正式規(guī)范的組成部分;提供這些頭文件只是為希望用一種標準化方法訪問本地繪圖功能的開發(fā)人員提供一種便利。我認為這表示將 JDK 移植到其它平臺的廠商可以不提供這個 API。
庫:一個以 jawt.lib 命名的新庫已添加到 SDK 的庫目錄中。如前所述,這個庫包含一個用于把 J2AWT 包含到您的應(yīng)用程序中所需要的入口點。例如,要鏈接到 GetDrawingSurfaceInfo 入口點,您需要在您的程序中包含 jawt.lib。
工具:javah 工具用來為 Java 類的本地函數(shù)生成 C/C++ 頭文件,javac 工具用來編譯 Java 源文件。
小結(jié)
將原有軟件系統(tǒng)移植到 Java 中并不容易,尤其是當原有軟件包含高性能的繪圖器時。Java 2 AWT 本地接口使得分階段移植變得較為容易,它允許您首先移植對性能要求不高的代碼,然后再移植關(guān)鍵的繪制代碼。它同時使第三方窗口小部件開發(fā)廠商更能嚴肅地看待針對 Java 產(chǎn)品的開發(fā)。有了 JDK1.3中的本地應(yīng)用程序接口,您就可以移植原有的 GUI 代碼,并更快地完成開發(fā),這樣就不會犧牲您為提高本地代碼關(guān)鍵部分的性能而作的投資。
【編輯推薦】