如果在Java程序中你使用Java Native Interface(JNI) 來(lái)調(diào)用某個(gè)特定平臺(tái)下的本地庫(kù)文件,你就會(huì)發(fā)現(xiàn)這個(gè)過(guò)程很單調(diào)、乏味。Jeff Friesen一直在介紹一個(gè)知名度很低的Java開(kāi)源項(xiàng)目:Java Native Access---它能夠避免因使用JNI導(dǎo)致的錯(cuò)誤和乏味,同時(shí)它還能讓你通過(guò)編程的方式調(diào)用C語(yǔ)言庫(kù)。
在Java語(yǔ)言沒(méi)有提供必要的APIs的情況下,Java程序使用Java Native Interface (JNI)來(lái)調(diào)用特定平臺(tái)下的本地庫(kù)是必要的。例如:在Windows XP平臺(tái)中,我使用過(guò)JNI來(lái)調(diào)用通用串行總線(xiàn)和基于TWAIN的掃描儀器的庫(kù);在更古老的Windows NT平臺(tái)中,調(diào)用過(guò)智能卡的庫(kù)。
我按照一個(gè)基本的、乏味的流程來(lái)解決這些問(wèn)題:首先,我創(chuàng)建一個(gè)Java類(lèi)用來(lái)載入JNI-friendly庫(kù)(這個(gè)庫(kù)能過(guò)訪(fǎng)問(wèn)其他的庫(kù))并且聲明這個(gè)類(lèi)的本地方法。然后,在使用JDK中的javah工具為JNI-friendly庫(kù)中的函數(shù)---函數(shù)和這個(gè)類(lèi)中的本地方法一一對(duì)應(yīng)---創(chuàng)建一個(gè)代理。最后,我使用C語(yǔ)言寫(xiě)了一個(gè)庫(kù)并用C編譯器編譯了這些代碼。
盡管完成這些流程并不是很困難,但是寫(xiě)C代碼是一個(gè)很緩慢的過(guò)程---例如: C語(yǔ)言中的字符串處理是通過(guò)指針來(lái)實(shí)現(xiàn)的,這會(huì)很復(fù)雜的。而且,使用JNI很容易出現(xiàn)錯(cuò)誤,導(dǎo)致內(nèi)存泄漏、很難找到程序崩潰的原因。
在Java開(kāi)源系列的第二篇文章中,我要介紹一個(gè)更簡(jiǎn)單、更安全的解決方法:Todd Fast and Timothy Wall的Java Native Access (JNA) 項(xiàng)目。JNA能夠讓你在Java程序中調(diào)用本地方法時(shí)避免使用C和Java Native Interface。在這篇文章中,讓我以簡(jiǎn)要的介紹 JNA和運(yùn)行示例必需的軟件來(lái)開(kāi)始下面的內(nèi)容。然后,向你展示如何使用JNA將3個(gè)Windows本地庫(kù)中的有用代碼移植到Java程序中。
Get started with JNA(JNA入門(mén))
Java Native Access 項(xiàng)目 在Java.net上,你可以到這個(gè)網(wǎng)站上現(xiàn)在這個(gè)項(xiàng)目的代碼和在線(xiàn)幫助文檔。雖然在下載有5個(gè)相關(guān)的jar文件,在本文中你僅僅需要下載其中的jna.jar和example.jar。
Jna.jar提供基本的、運(yùn)行這些示例文件必需的jna運(yùn)行環(huán)境。這個(gè)jna.jar文件除了有Unix、Linux、Windows和Mac OS X平臺(tái)相關(guān)的JNT-friendly本地庫(kù)外,還包含其他幾個(gè)類(lèi)包。每一個(gè)本地庫(kù)都是用來(lái)訪(fǎng)問(wèn)相對(duì)應(yīng)平臺(tái)下的本地方法的。
example.jar包含了不同的示例來(lái)表明JNA的用途。其中的一個(gè)例子是使用JNA來(lái)實(shí)現(xiàn)一個(gè)在不同平臺(tái)下的透明視窗技術(shù)的API。在文章最后的示例中將要展示如何使用這個(gè)API修復(fù)上個(gè)月的文章關(guān)于VerifyAge2應(yīng)用中辨認(rèn)透明效果的問(wèn)題。
獲取本地時(shí)間(Get local time)
如果你在Java Native Access 首頁(yè) 看過(guò)“JNA如何入門(mén)”,你就會(huì)知道一個(gè)很簡(jiǎn)單的關(guān)于調(diào)用Windows 平臺(tái)下的API函數(shù):GetSystemTime() 的JNA示例。這個(gè)不完整的例子只是展示了JNA的基本特點(diǎn)。(在例子的基礎(chǔ)上,我做了一個(gè)更完整的基于Windows的例子來(lái)介紹JNA)我在Windows平臺(tái)下完善了這個(gè)例子來(lái)介紹JNA。
第一例子基于Windows GetLocalTime() API函數(shù)返回本地當(dāng)前的時(shí)間和日期。和GetSystemTime()不同的是,返回的時(shí)間/日期是協(xié)調(diào)通用時(shí)間(UTC)格式的,GetLocalTime()返回的時(shí)間/日期信息的格式是根據(jù)當(dāng)前時(shí)區(qū)來(lái)表示。
在一個(gè)Java程序中使用JNA調(diào)用GetLocalTime,你需要知道這個(gè)函數(shù)所在的Windows平臺(tái)下的動(dòng)態(tài)鏈接庫(kù)(DLL)的名稱(chēng)(和可能所在的地理區(qū)域)。我們發(fā)現(xiàn)GetLocalTime()和GetSystemTime在同一個(gè)DLL文件中:kernel32.dll。你還需要知道GetLocalTime()在C語(yǔ)言環(huán)境中的申明。申明如下Listing 1:
Listing 1. GetLocalTime在C語(yǔ)言中的申明
typedef struct
{
WORD wYear;
WORD wMonth;
WORD wDayOfWeek;
WORD wDay;
WORD wHour;
WORD wMinute;
WORD wSecond;
WORD wMilliseconds;
}
SYSTEMTIME, *LPSYSTEMTIME;
VOID GetLocalTime(LPSYSTEMTIME lpst);
這個(gè)基于C語(yǔ)言的申明表明傳到這個(gè)函數(shù)的參數(shù)數(shù)目和類(lèi)型。在這個(gè)例子中,只有一個(gè)參數(shù)---一個(gè)指向Windows SYSTEMTIME結(jié)構(gòu)體的指針。而且,每個(gè)結(jié)構(gòu)體成員的類(lèi)型是16bit長(zhǎng)度的無(wú)符號(hào)整型。根據(jù)這些信息,你能夠創(chuàng)建一個(gè)完全描述GetLocalTime()函數(shù)的接口,如Listing 2中所示:
Listing 2. Kernel32.java
// Kernel32.java
import com.sun.jna.*;
import com.sun.jna.win32.*;
public interface Kernel32 extends StdCallLibrary
{
public static class SYSTEMTIME extends Structure
{
public short wYear;
public short wMonth;
public short wDayOfWeek;
public short wDay;
public short wHour;
public short wMinute;
public short wSecond;
public short wMilliseconds;
}
void GetLocalTime (SYSTEMTIME result);
}
Kernel32 接口(The Kernel32 interface)
因?yàn)镴NA使用通過(guò)一個(gè)接口來(lái)訪(fǎng)問(wèn)某個(gè)庫(kù)中的函數(shù),Listing 2表示了一個(gè)描述GetLocalTime()的接口。根據(jù)約定,我把接口命名為Kernel32是因?yàn)镚etLocalTime()在Windows的kernel32.dll庫(kù)。
這個(gè)接口必須繼承com.sun..jna.Library接口。因?yàn)閃indows API函數(shù)遵循stdcall調(diào)用協(xié)議(stdcall calling convention),為Windows API申明的接口也必須繼承com.sun.jna.win32. StdCallLibrary接口。因此這個(gè)接口共繼承了Library 和 com.sun.jna.win32.StdCall兩個(gè)接口。
在前面,你已經(jīng)知道了GetLocalTime() 需要一個(gè)指向SYSTEMTIME結(jié)構(gòu)體的指針作為它唯一的參數(shù)。因?yàn)镴ava不支持指針,JNA是通過(guò)申明一個(gè)com.sun.jna.Structure的子類(lèi)來(lái)代替的。根據(jù)java文檔中抽象類(lèi)的概念,在參數(shù)環(huán)境中,Structure相當(dāng)于C語(yǔ)言的struct*。
在SYSTEMTIME類(lèi)中的字段和C結(jié)構(gòu)體中的相對(duì)應(yīng)的屬性字段的順序是一一對(duì)應(yīng)的。保證字段順序的一致性是非常重要的。例如,我發(fā)現(xiàn)交換wYear和wMonth會(huì)導(dǎo)致wYear和wMonth值互換。
每個(gè)字段在java中是short integer類(lèi)型的。按照J(rèn)NA首頁(yè)上 “默認(rèn)類(lèi)型映射”章節(jié)給出的提示,這個(gè)short integer分配類(lèi)型是正確。然而,我們應(yīng)該知道一個(gè)重要的區(qū)別:Windows平臺(tái)下的WORD類(lèi)型等同于C語(yǔ)言環(huán)境中的16-bit的無(wú)符號(hào)的short integer,而java中short integer是16-bit有符號(hào)的short integer。
一個(gè)類(lèi)型映射的問(wèn)題
通過(guò)比較一個(gè)API 函數(shù)返回的整型值,你會(huì)發(fā)現(xiàn)Windows/C 語(yǔ)言的無(wú)符號(hào)整型和Java語(yǔ)言的有符號(hào)整型的JNA類(lèi)型映射是有問(wèn)題的。在比較的過(guò)程中,如果你不細(xì)心,那么錯(cuò)誤的執(zhí)行過(guò)程可能導(dǎo)致決定性情況。導(dǎo)致這種后果是因?yàn)橥浫魏螖?shù)值的符號(hào)位的確定是根據(jù):在無(wú)符號(hào)整型的情況下會(huì)被解釋為正號(hào),而在有符號(hào)整型的進(jìn)制中被理解為負(fù)號(hào)的。
通過(guò)Kernel32獲取本地時(shí)間(Access the local time with Kernel32)
JNA首頁(yè)上的GetSystemTime()示例已經(jīng)表明必須使用預(yù)先申明的接口為本地庫(kù)分配一個(gè)實(shí)例對(duì)象。你可以通過(guò)com.sun.jna.Native類(lèi)中靜態(tài)公用方法loadLibrary(String name, Class interfaceClass)來(lái)完成上述的目標(biāo)。Listing 3 所示:
Listing 3. LocalTime.java
// LocalTime.java
import com.sun.jna.*;
public class LocalTime
{
public static void main (String [] args)
{
Kernel32 lib = (Kernel32) Native.loadLibrary ("kernel32",
Kernel32.class);
Kernel32.SYSTEMTIME time = new Kernel32.SYSTEMTIME ();
lib.GetLocalTime (time);
System.out.println ("Year is "+time.wYear);
System.out.println ("Month is "+time.wMonth);
System.out.println ("Day of Week is "+time.wDayOfWeek);
System.out.println ("Day is "+time.wDay);
System.out.println ("Hour is "+time.wHour);
System.out.println ("Minute is "+time.wMinute);
System.out.println ("Second is "+time.wSecond);
System.out.println ("Milliseconds are "+time.wMilliseconds);
}
}
Listing 3 執(zhí)行Kernel32 lib = (Kernel32) Native.loadLibrary ("kernel32", Kernel32.class);來(lái)分配一個(gè)Kernel32實(shí)例對(duì)象并且裝載kernel32.dll。因?yàn)閗ernel32.dll是Windows平臺(tái)下標(biāo)準(zhǔn)的dll文件,所以不要指定訪(fǎng)問(wèn)這個(gè)庫(kù)的路徑。然而,如果找不到這個(gè)dll文件,loadLibrary()會(huì)拋出一個(gè)UnsatisfiedLinkError異常。
Kernel32.SYSTEMTIME time = new Kernel32.SYSTEMTIME ();創(chuàng)建了一個(gè)SYSTEMTIME結(jié)構(gòu)體的示例。初始化后下面是lib.GetLocalTime (time);,這句話(huà)使用本地的時(shí)間/日期來(lái)給這個(gè)實(shí)例賦值。幾個(gè)System.out.println()語(yǔ)句是輸出這些值。
編譯和運(yùn)行這個(gè)應(yīng)用(Compile and run the application)
這部分很容易。假設(shè)jna.jar、Kernel32.java和LocalTime.java是放在當(dāng)前文件夾中,調(diào)用java –cp jna.jar;. LocalTime.java來(lái)編譯這個(gè)應(yīng)用的源代碼。如果在Windows平臺(tái)下,調(diào)用invoke java –cp jna.jar;. LocalTime 來(lái)運(yùn)行這個(gè)應(yīng)用。你可以得到類(lèi)似與Listing 4的輸出結(jié)果:
Listing 4. 從LocalTime.java生成的輸出
Year is 2007
Month is 12
Day of Week is 3
Day is 19
Hour is 12
Minute is 35
Second is 13
Milliseconds are 156
獲取操縱桿信息(Accessing joystick device info)
上面的例子已經(jīng)介紹了JNA,但是這個(gè)獲取本地時(shí)間和日期的例子并沒(méi)有很好的利用這個(gè)技術(shù),甚至也沒(méi)有體現(xiàn)JNI的價(jià)值。Java語(yǔ)言中的System.currentTimeMillis()函數(shù)已經(jīng)以毫秒的格式返回了這些信息。因?yàn)镴ava語(yǔ)言沒(méi)有為游戲控制器提供API,所以獲取操縱桿的信息更適合JNA的使用。
例如,你要構(gòu)建一個(gè)平臺(tái)無(wú)關(guān)的Java庫(kù),而且這些庫(kù)使用JNA調(diào)用Linux, Mac OS X, Windwos和Unix平臺(tái)中本地的操縱桿API。為了簡(jiǎn)潔和方便起見(jiàn),這個(gè)例子僅僅是調(diào)用Windows平臺(tái)下的操縱桿API。而且我將重點(diǎn)介紹這個(gè)API很小的一部分。
類(lèi)似GetLocalTime(),第一步是辨別出操作桿API的DLL,這個(gè)DLL是winmm.dll,和kernel32.dll在同一個(gè)文件夾中,它包含了操作桿的API和其他的多媒體APIs。還需知道要被使用的操作桿函數(shù)基于C語(yǔ)言的聲明。這些函數(shù)聲明已經(jīng)在Listing 5中列出來(lái)了。
Listing 5. C-based declarations for some Joystick API functions
#define MAXPNAMELEN 32
typedef struct
{
WORD wMid; // manufacturer identifier
WORD wPid; // product identifier
TCHAR szPname MAXPNAMELEN ; // product name
UINT wXmin; // minimum x position
UINT wXmax; // maximum x position
UINT wYmin; // minimum y position
UINT wYmax; // maximum y position
UINT wZmin; // minimum z position
UINT wZmax; // maximum z position
UINT wNumButtons; // number of buttons
UINT wPeriodMin; // smallest supported polling interval when captured
UINT wPeriodMax; // largest supported polling interval when captured
}
JOYCAPS, *LPJOYCAPS;
MMRESULT joyGetDevCaps(UINT IDDevice, LPJOYCAPS lpjc, UINT cbjc);
UINT joyGetNumDevs(VOID);
操作桿API的函數(shù)(Functions of the Joystick API)
在Windows平臺(tái)下是通過(guò)以joy作為函數(shù)名開(kāi)始的函數(shù)以及被各種函數(shù)調(diào)用的結(jié)構(gòu)體來(lái)實(shí)現(xiàn)操作桿API的。例如,joyGetNumDevs()返回的是這個(gè)平臺(tái)下支持的操作桿設(shè)備最多的數(shù)目;joyGetDevCaps()返回的是每個(gè)連接上的操縱桿的質(zhì)量。
joyGetDevCaps()函數(shù)需要3個(gè)參數(shù):
* 處在0到j(luò)oyGetNumDevs()-1之間的操作桿ID
* 保存返回的質(zhì)量信息的JOYCAPS結(jié)構(gòu)體的地址
* JOYCAPS結(jié)構(gòu)體的字節(jié)大小
雖然它的結(jié)果不同,這個(gè)函數(shù)返回的是一個(gè)32位的無(wú)符號(hào)整型結(jié)果,而且0表示一個(gè)已經(jīng)連接的操縱桿。
JOYCAPS結(jié)構(gòu)體有3種類(lèi)型。Windows平臺(tái)下的WORD(16位無(wú)符號(hào)短整型)類(lèi)型對(duì)應(yīng)的是Java語(yǔ)言中16位有符號(hào)短整型。除此之外,Windows下的UINT(32位無(wú)符號(hào)整型)類(lèi)型是和Java語(yǔ)言中32位有符號(hào)整型相對(duì)應(yīng)的。而Windows平臺(tái)上的text character就是TCHAR類(lèi)型。
微軟通過(guò)TCHAR類(lèi)型使開(kāi)發(fā)人員能夠從ASCII類(lèi)型的函數(shù)參數(shù)平滑的轉(zhuǎn)移到Unicode字符類(lèi)型的函數(shù)參數(shù)上。而且,擁有text類(lèi)型參數(shù)的函數(shù)的實(shí)現(xiàn)是通過(guò)宏轉(zhuǎn)變?yōu)閷?duì)應(yīng)的ASCII或者wide-character的函數(shù)。例如,joyGetDevCaps()是一個(gè)對(duì)應(yīng)joyGetDevCapsA() 和 joyGetDevCapsW()的宏。
使用TCHAR(Working with TCHAR)
使用TCHAR和將TCHAR轉(zhuǎn)變的宏會(huì)導(dǎo)致基于C語(yǔ)言的申明向基于JNA接口的轉(zhuǎn)換
變得有點(diǎn)復(fù)雜—你在使用ASCII或者wide-character版本的操縱桿函數(shù)嗎??jī)煞N版本都在如下的接口中展示了:
Listing 6. WinMM.java
// WinMM.java
import com.sun.jna.*;
import com.sun.jna.win32.*;
public interface WinMM extends StdCallLibrary
{
final static int JOYCAPSA_SIZE = 72;
public static class JOYCAPSA extends Structure
{
public short wMid;
public short wPid;
public byte szPname [] = new byte [32];
public int wXmin;
public int wXmax;
public int wYmin;
public int wYmax;
public int wZmin;
public int wZmax;
public int wNumButtons;
public int wPeriodMin;
public int wPeriodMax;
}
int joyGetDevCapsA (int id, JOYCAPSA caps, int size);
final static int JOYCAPSW_SIZE = 104;
public static class JOYCAPSW extends Structure
{
public short wMid;
public short wPid;
public char szPname [] = new char [32];
public int wXmin;
public int wXmax;
public int wYmin;
public int wYmax;
public int wZmin;
public int wZmax;
public int wNumButtons;
public int wPeriodMin;
public int wPeriodMax;
}
int joyGetDevCapsW (int id, JOYCAPSW caps, int size);
int joyGetNumDevs ();
}
Listing 6沒(méi)有介紹JNA的新特性。實(shí)際上,JNA強(qiáng)調(diào)了對(duì)本地庫(kù)的接口命名規(guī)則。同時(shí),還展示了如何將TCHAR映射到Java語(yǔ)言中的byte和char數(shù)組。最后,它揭示了以常量方式聲明的結(jié)構(gòu)體的大小。Listing 7展示了當(dāng)調(diào)用joyGetDevCapsA() 和 joyGetDevCapsW()時(shí)如何使用這些常量。
Listing 7. JoystickInfo.java
// JoystickInfo.java
import com.sun.jna.*;
public class JoystickInfo
{
public static void main (String [] args)
{
WinMM lib = (WinMM) Native.loadLibrary ("winmm", WinMM.class);
int numDev = lib.joyGetNumDevs ();
System.out.println ("joyGetDevCapsA() Demo");
System.out.println ("---------------------\n");
WinMM.JOYCAPSA caps1 = new WinMM.JOYCAPSA ();
for (int i = 0; i < numDev; i++)
if (lib.joyGetDevCapsA (i, caps1, WinMM.JOYCAPSA_SIZE) == 0)
{
String pname = new String (caps1.szPname);
pname = pname.substring (0, pname.indexOf ('\0'));
System.out.println ("Device #"+i);
System.out.println (" wMid = "+caps1.wMid);
System.out.println (" wPid = "+caps1.wPid);
System.out.println (" szPname = "+pname);
System.out.println (" wXmin = "+caps1.wXmin);
System.out.println (" wXmax = "+caps1.wXmax);
System.out.println (" wYmin = "+caps1.wYmin);
System.out.println (" wYmax = "+caps1.wYmax);
System.out.println (" wZmin = "+caps1.wZmin);
System.out.println (" wZmax = "+caps1.wZmax);
System.out.println (" wNumButtons = "+caps1.wNumButtons);
System.out.println (" wPeriodMin = "+caps1.wPeriodMin);
System.out.println (" wPeriodMax = "+caps1.wPeriodMax);
System.out.println ();
}
System.out.println ("joyGetDevCapsW() Demo");
System.out.println ("---------------------\n");
WinMM.JOYCAPSW caps2 = new WinMM.JOYCAPSW ();
for (int i = 0; i < numDev; i++)
if (lib.joyGetDevCapsW (i, caps2, WinMM.JOYCAPSW_SIZE) == 0)
{
String pname = new String (caps2.szPname);
pname = pname.substring (0, pname.indexOf ('\0'));
System.out.println ("Device #"+i);
System.out.println (" wMid = "+caps2.wMid);
System.out.println (" wPid = "+caps2.wPid);
System.out.println (" szPname = "+pname);
System.out.println (" wXmin = "+caps2.wXmin);
System.out.println (" wXmax = "+caps2.wXmax);
System.out.println (" wYmin = "+caps2.wYmin);
System.out.println (" wYmax = "+caps2.wYmax);
System.out.println (" wZmin = "+caps2.wZmin);
System.out.println (" wZmax = "+caps2.wZmax);
System.out.println (" wNumButtons = "+caps2.wNumButtons);
System.out.println (" wPeriodMin = "+caps2.wPeriodMin);
System.out.println (" wPeriodMax = "+caps2.wPeriodMax);
System.out.println ();
}
}
}
盡管和LocalTime這個(gè)示例類(lèi)似,JoystickInfo執(zhí)行WinMM lib = (WinMM) Native.loadLibrary ("winmm", WinMM.class);這句話(huà)來(lái)獲取一個(gè)WinMM的實(shí)例,并且載入winmm.dll。它還執(zhí)行WinMM.JOYCAPSA caps1 = new WinMM.JOYCAPSA (); 和 WinMM.JOYCAPSW caps2 = new WinMM.JOYCAPSW ();初始化必需的結(jié)構(gòu)體實(shí)例。
編譯和運(yùn)行這個(gè)程序(Compile and run the application)
假如jna.jar,WinMM.java和JoystickInfo.java在同一個(gè)文件夾中,調(diào)用 javac -cp jna.jar;. JoystickInfo.java 來(lái)編譯這個(gè)應(yīng)用的源代碼。
在windows平臺(tái)下,調(diào)用java -cp jna.jar;. JoystickInfo就可以運(yùn)行這個(gè)應(yīng)用程序了。如果沒(méi)有操縱桿設(shè)備,你應(yīng)該得到Listing 8中的輸出。
將C語(yǔ)言中的string類(lèi)型轉(zhuǎn)換為Java語(yǔ)言的String類(lèi)型
pname = pname.substring (0, pname.indexOf ('\0')); 這段代碼將一個(gè)C string 轉(zhuǎn)換成了Java string. 如果不使用這個(gè)轉(zhuǎn)換,C語(yǔ)言的string結(jié)束符’\0’和string后面的無(wú)用字符都會(huì)成為Java語(yǔ)言中String實(shí)例對(duì)象的內(nèi)容。
Listing 8. 輸出操縱桿信息(Output of JoystickInfo)
joyGetDevCapsA() Demo
---------------------
joyGetDevCapsW() Demo
---------------------
上面的輸出是因?yàn)槊看握{(diào)用joyGetDevCap()返回的是一個(gè)非空值,這表示沒(méi)有操縱桿/游戲控制器設(shè)備或者是出現(xiàn)錯(cuò)誤。為了獲取更多有意思的輸出,將一個(gè)設(shè)備連接到你的平臺(tái)上并且再次運(yùn)行JoystickInfo。如下,將一個(gè)微軟SideWinder即插即用游戲觸摸板設(shè)備聯(lián)上之后我獲取了如下的輸出:
Listing 9. 操縱桿連接上之后的運(yùn)行結(jié)果(Output after running JoystickInfo with a joystick attached)
joyGetDevCapsA() Demo
---------------------
Device #0
wMid = 1118
wPid = 39
szPname = Microsoft PC-joystick driver
wXmin = 0
wXmax = 65535
wYmin = 0
wYmax = 65535
wZmin = 0
wZmax = 65535
wNumButtons = 6
wPeriodMin = 10
wPeriodMax = 1000
joyGetDevCapsW() Demo
---------------------
Device #0
wMid = 1118
wPid = 39
szPname = Microsoft PC-joystick driver
wXmin = 0
wXmax = 65535
wYmin = 0
wYmax = 65535
wZmin = 0
wZmax = 65535
wNumButtons = 6
wPeriodMin = 10
wPeriodMax = 1000
窗口透明度(Transparent windows)
在這系列文章中上篇文章是關(guān)于Bernhard Pauler's 氣泡提示(balloontip)工程的。我構(gòu)建了一個(gè)叫做VerifyAge的、包含有一個(gè)氣泡提示的GUI應(yīng)用。Figure 1中顯示了這個(gè)GUI應(yīng)用的一個(gè)小問(wèn)題:這個(gè)氣泡提示的沒(méi)有經(jīng)過(guò)修飾的對(duì)話(huà)框部分遮住了應(yīng)用窗口的一部分邊框,導(dǎo)致了無(wú)法點(diǎn)擊這個(gè)邊框的最小化和最大化按鈕,并且使整個(gè)GUI很難看.

盡管未修飾部分的對(duì)話(huà)框不能顯示氣泡提示的透明度,java語(yǔ)言不支持窗口透明度。幸運(yùn)的是,我們可以通過(guò)使用com.sun.jna.examples.WindowUtils類(lèi)調(diào)用JNA的examples.jar文件來(lái)解決這個(gè)問(wèn)題。
WindowUtils提供在Unix,Linux,Mac OS X和Windows平臺(tái)上使用JNA’s來(lái)實(shí)現(xiàn)窗口透明的工具方法。例如, public static void setWindowMask(final Window w, Icon mask) 讓你根據(jù)像素而不是通過(guò)預(yù)定的掩罩(mask)參數(shù)來(lái)選取某部分的窗口。這個(gè)功能將在Listing 10中展示:
Listing 10. Using JNA to render a window transparent
// Create a mask for this dialog. This mask has the same shape as the
// dialog's rounded balloon tip and ensures that only the balloon tip
// part of the dialog will be visible. All other dialog pixels will
// disappear because they correspond to transparent mask pixels.
// Note: The drawing code is based on the drawing code in
// RoundedBalloonBorder.
Rectangle bounds = getBounds ();
BufferedImage bi = new BufferedImage (bounds.width, bounds.height,
BufferedImage.TYPE_INT_ARGB);
Graphics g = bi.createGraphics ();
g.fillRoundRect (0, 0, bounds.width, bounds.height-VERT_OFFSET,
ARC_WIDTH*2, ARC_HEIGHT*2);
g.drawRoundRect (0, 0, bounds.width-1, bounds.height-VERT_OFFSET-1,
ARC_WIDTH*2, ARC_HEIGHT*2);
int [] xPoints = { HORZ_OFFSET, HORZ_OFFSET+VERT_OFFSET, HORZ_OFFSET };
int [] yPoints = { bounds.height-VERT_OFFSET-1, bounds.height-VERT_OFFSET
-1, bounds.height-1 };
g.fillPolygon (xPoints, yPoints, 3);
g.drawLine (xPoints [0], yPoints [0], xPoints [2], yPoints [2]);
g.drawLine (xPoints [1], yPoints [1], xPoints [2], yPoints [2]);
g.dispose ();
WindowUtils.setWindowMask (this, new ImageIcon (bi));
在Listing 10中的代碼段是從本文代碼文檔(
code archive)里的加強(qiáng)版的VerifyAge2 應(yīng)用中的TipFrame的構(gòu)造函數(shù)結(jié)尾部分摘錄的。這個(gè)構(gòu)造函數(shù)定義了圍繞提示氣泡的掩罩(mask)的形狀,在這個(gè)形狀范圍里描繪不透明的像素。
假如你當(dāng)前文件夾中有examples.jar, jna.jar, 和 VerifyAge2.java,調(diào)用 javac -cp examples.jar;balloontip.jar VerifyAge2.java 來(lái)編譯源文件.然后調(diào)用java -Dsun.java2d.noddraw=true -cp examples.jar;balloontip.jar;. VerifyAge2運(yùn)行這個(gè)應(yīng)用. Figure 2 展示了透明示例.
總結(jié)(In conclusion)
JNA項(xiàng)目有很長(zhǎng)的歷史了(追溯到1999年),但是它第一次發(fā)布是在2006年11月。從此以后它慢慢的被需要將本地C代碼整合到Java工程中的開(kāi)發(fā)者注意到了。因?yàn)镴NA能夠用來(lái)解決JuRuby中常見(jiàn)一個(gè)問(wèn)題:缺乏對(duì)POSIX調(diào)用的支持(
lack of support for POSIX calls),它也在JRuby程序員中掀起些波浪。JNA也同樣被作為實(shí)現(xiàn)用低級(jí)C代碼繼承Ruby的一種解決方案(
extending Ruby with low-level C code)。
我喜歡使用JNA來(lái)工作,相信你也會(huì)發(fā)現(xiàn)它比使用JNI來(lái)訪(fǎng)問(wèn)本地代碼更簡(jiǎn)單、更安全。無(wú)需多言,JNA還有更多的特性在本文中沒(méi)有體現(xiàn)出來(lái)。查閱它的資源部分:獲取這個(gè)開(kāi)源java項(xiàng)目更多的信息(
learn more about this open source Java project)。用它做demo,而且在論壇(
discussion forum )上共享你的經(jīng)驗(yàn)。 下一個(gè)月我會(huì)帶著另一個(gè)開(kāi)源項(xiàng)目回來(lái)的,這個(gè)開(kāi)源項(xiàng)目會(huì)給你每天的java開(kāi)發(fā)帶來(lái)益處。
附錄:WindowUtils.setWindowMask()的替代品
在剛剛寫(xiě)完這篇文章后,我發(fā)現(xiàn)java語(yǔ)言支持在6u10版本中支持窗口的透明和形狀定制。讀完Kirill Grouchnikov的博客后,我用WindowUtils.setWindowMask()的替代品修改了VerifyAge2,如下:
// Create and install a balloon tip shape to ensure that only this part
// of the dialog will be visible.
Rectangle bounds = getBounds ();
GeneralPath gp;
gp = new GeneralPath (new RoundRectangle2D.Double (bounds.x, bounds.y,
bounds.width,
bounds.height-
VERT_OFFSET,
ARC_WIDTH*2-1,
ARC_HEIGHT*2-1));
gp.moveTo (HORZ_OFFSET, bounds.height-VERT_OFFSET);
gp.lineTo (HORZ_OFFSET, bounds.height);
gp.lineTo (HORZ_OFFSET+VERT_OFFSET+1, bounds.height-VERT_OFFSET);
AWTUtilities.setWindowShape (this, gp);
這段代碼使用新類(lèi)AWTUtilities(在com.sun.awt包中),而且public void setWindowShape(Window w, Shape s)函數(shù)將TipFrame和JDialog窗口設(shè)置氣泡形狀。
posted on 2009-05-07 11:08
lanxin1020 閱讀(817)
評(píng)論(0) 編輯 收藏 所屬分類(lèi):
j2se