开源项目JNA
2008-10-31 18:12
337 查看
开源项目:JavaNativeAccess
用更容易的方法来调用本地代码ByJeffFriesen,JavaWorld.com,02/05/08
如果在Java程序中你使用JavaNativeInterface(JNI)来调用某个特定平台下的本地库文件,你就会发现这个过程很单调、乏味。JeffFriesen一直在介绍一个知名度很低的Java开源项目:JavaNativeAccess---它能够避免因使用JNI导致的错误和乏味,同时它还能让你通过编程的方式调用C语言库。
在Java语言没有提供必要的APIs的情况下,Java程序使用JavaNativeInterface(JNI)来调用特定平台下的本地库是必要的。例如:在WindowsXP平台中,我使用过JNI来调用通用串行总线和基于TWAIN的扫描仪器的库;在更古老的WindowsNT平台中,调用过智能卡的库。
我按照一个基本的、乏味的流程来解决这些问题:首先,我创建一个Java类用来载入JNI-friendly库(这个库能够访问其他的库)并且声明这个类的本地方法。然后,在使用JDK中的javah工具为JNI-friendly库中的函数---函数和这个类中的本地方法一一对应---创建一个代理。最后,我使用C语言写了一个库并用C编译器编译了这些代码。
尽管完成这些流程并不是很困难,但是写C代码是一个很缓慢的过程---例如:C语言中的字符串处理是通过指针来实现的,这会很复杂的。而且,使用JNI很容易出现错误,导致内存泄漏、很难找到程序崩溃的原因。
在
GetstartedwithJNA(JNA入门)
Jna.jar提供基本的、运行这些示例文件必需的jna运行环境。这个jna.jar文件除了有Unix、Linux、Windows和MacOSX平台相关的JNT-friendly本地库外,还包含其他几个类包。每一个本地库都是用来访问相对应平台下的本地方法的。
example.jar包含了不同的示例来表明JNA的用途。其中的一个例子是使用JNA来实现一个在不同平台下的透明视窗技术的API。在文章最后的示例中将要展示如何使用这个API修复上个月的文章关于VerifyAge2应用中辨认透明效果的问题。
获取本地时间(Getlocaltime)
如果你在第一例子基于WindowsGetLocalTime()API函数返回本地当前的时间和日期。和GetSystemTime()不同的是,返回的时间/日期是
在一个Java程序中使用JNA调用GetLocalTime,你需要知道这个函数所在的Windows平台下的动态链接库(DLL)的名称(和可能所在的地理区域)。我们发现GetLocalTime()和GetSystemTime在同一个DLL文件中:kernel32.dll。你还需要知道GetLocalTime()在C语言环境中的申明。申明如下Listing1:
Listing1.GetLocalTime在C语言中的申明
typedefstruct
{
WORDwYear;
WORDwMonth;
WORDwDayOfWeek;
WORDwDay;
WORDwHour;
WORDwMinute;
WORDwSecond;
WORDwMilliseconds;
}
SYSTEMTIME,*LPSYSTEMTIME;
VOIDGetLocalTime(LPSYSTEMTIMElpst);
这个基于C语言的申明表明传到这个函数的参数数目和类型。在这个例子中,只有一个参数---一个指向WindowsSYSTEMTIME结构体的指针。而且,每个结构体成员的类型是16bit长度的无符号整型。根据这些信息,你能够创建一个完全描述GetLocalTime()函数的接口,如Listing2中所示:
Listing2.Kernel32.java
//Kernel32.java
importcom.sun.jna.*;
importcom.sun.jna.win32.*;
publicinterfaceKernel32extendsStdCallLibrary
{
publicstaticclassSYSTEMTIMEextendsStructure
{
publicshortwYear;
publicshortwMonth;
publicshortwDayOfWeek;
publicshortwDay;
publicshortwHour;
publicshortwMinute;
publicshortwSecond;
publicshortwMilliseconds;
}
voidGetLocalTime(SYSTEMTIMEresult);
}
Kernel32接口(TheKernel32interface)
因为JNA使用通过一个接口来访问某个库中的函数,Listing2表示了一个描述GetLocalTime()的接口。根据约定,我把接口命名为Kernel32是因为GetLocalTime()在Windows的kernel32.dll库。这个接口必须继承com.sun..jna.Library接口。因为WindowsAPI函数遵循stdcall调用协议(
在前面,你已经知道了GetLocalTime()需要一个指向SYSTEMTIME结构体的指针作为它唯一的参数。因为Java不支持指针,JNA是通过申明一个com.sun.jna.Structure的子类来代替的。根据java文档中抽象类的概念,在参数环境中,Structure相当于C语言的struct*。
一个类型映射的问题 |
通过比较一个API函数返回的整型值,你会发现Windows/C语言的无符号整型和Java语言的有符号整型的JNA类型映射是有问题的。在比较的过程中,如果你不细心,那么错误的执行过程可能导致决定性情况。导致这种后果是因为忘记任何数值的符号位的确定是根据:在无符号整型的情况下会被解释为正号,而在有符号整型的进制中被理解为负号的。 |
证字段顺序的一致性是非常重要的。例如,我发现交换wYear和wMonth会导致wYear和wMonth值互换。
每个字段在java中是shortinteger类型的。按照JNA首页上“默认类型映射”章节给出的提示,这个shortinteger分配类型是正确。然而,我们应该知道一个重要的区别:Windows平台下的WORD类型等同于C语言环境中的16-bit的无符号的shortinteger,而java中shortinteger是16-bit有符号的shortinteger。
通过Kernel32获取本地时间(AccessthelocaltimewithKernel32)
JNA首页上的GetSystemTime()示例已经表明必须使用预先申明的接口为本地库分配一个实例对象。你可以通过com.sun.jna.Native类中静态公用方法loadLibrary(Stringname,ClassinterfaceClass)来完成上述的目标。Listing3所示:Listing3.LocalTime.java
//LocalTime.java
importcom.sun.jna.*;
publicclassLocalTime
{
publicstaticvoidmain(String[]args)
{
Kernel32lib=(Kernel32)Native.loadLibrary("kernel32",
Kernel32.class);
Kernel32.SYSTEMTIMEtime=newKernel32.SYSTEMTIME();
lib.GetLocalTime(time);
System.out.println("Yearis"+time.wYear);
System.out.println("Monthis"+time.wMonth);
System.out.println("DayofWeekis"+time.wDayOfWeek);
System.out.println("Dayis"+time.wDay);
System.out.println("Houris"+time.wHour);
System.out.println("Minuteis"+time.wMinute);
System.out.println("Secondis"+time.wSecond);
System.out.println("Millisecondsare"+time.wMilliseconds);
}
}
Listing3执行Kernel32lib=(Kernel32)Native.loadLibrary("kernel32",Kernel32.class);来分配一个Kernel32实例对象并且装载kernel32.dll。因为kernel32.dll是Windows平台下标准的dll文件,所以不要指定访问这个库的路径。然而,如果找不到这个dll文件,loadLibrary()会抛出一个UnsatisfiedLinkError异常。
Kernel32.SYSTEMTIMEtime=newKernel32.SYSTEMTIME();创建了一个SYSTEMTIME结构体的示例。初始化后下面是lib.GetLocalTime(time);,这句话使用本地的时间/日期来给这个实例赋值。几个System.out.println()语句是输出这些值。
编译和运行这个应用(Compileandruntheapplication)
这部分很容易。假设jna.jar、Kernel32.java和LocalTime.java是放在当前文件夹中,调用java–cpjna.jar;.LocalTime.java来编译这个应用的源代码。如果在Windows平台下,调用invokejava–cpjna.jar;.LocalTime来运行这个应用。你可以得到类似与Listing4的输出结果:Listing4.从LocalTime.java生成的输出
Yearis2007
Monthis12
DayofWeekis3
Dayis19
Houris12
Minuteis35
Secondis13
Millisecondsare156
获取操纵杆信息(Accessingjoystickdeviceinfo)
上面的例子已经介绍了JNA,但是这个获取本地时间和日期的例子并没有很好的利用这个技术,甚至也没有体现JNI的价值。Java语言中的System.currentTimeMillis()函数已经以毫秒的格式返回了这些信息。因为Java语言没有为游戏控制器提供API,所以获取操纵杆的信息更适合JNA的使用。例如,你要构建一个平台无关的Java库,而且这些库使用JNA调用Linux,MacOSX,Windwos和Unix平台中本地的操纵杆API。为了简洁和方便起见,这个例子仅仅是调用Windows平台下的操纵杆API。而且我将重点介绍这个API很小的一部分。
类似GetLocalTime(),第一步是辨别出操作杆API的DLL,这个DLL是winmm.dll,和kernel32.dll在同一个文件夹中,它包含了操作杆的API和其他的多媒体APIs。还需知道要被使用的操作杆函数基于C语言的声明。这些函数声明已经在Listing5中列出来了。
Listing5.C-baseddeclarationsforsomeJoystickAPIfunctions
#defineMAXPNAMELEN32
typedefstruct
{
WORDwMid;//manufactureridentifier
WORDwPid;//productidentifier
TCHARszPname[MAXPNAMELEN];//productname
UINTwXmin;//minimumxposition
UINTwXmax;//maximumxposition
UINTwYmin;//minimumyposition
UINTwYmax;//maximumyposition
UINTwZmin;//minimumzposition
UINTwZmax;//maximumzposition
UINTwNumButtons;//numberofbuttons
UINTwPeriodMin;//smallestsupportedpollingintervalwhencaptured
UINTwPeriodMax;//largestsupportedpollingintervalwhencaptured
}
JOYCAPS,*LPJOYCAPS;
MMRESULTjoyGetDevCaps(UINTIDDevice,LPJOYCAPSlpjc,UINTcbjc);
UINTjoyGetNumDevs(VOID);
操作杆API的函数(FunctionsoftheJoystickAPI)
在Windows平台下是通过以joy作为函数名开始的函数以及被各种函数调用的结构体来实现操作杆API的。例如,joyGetNumDevs()返回的是这个平台下支持的操作杆设备最多的数目;joyGetDevCaps()返回的是每个连接上的操纵杆的质量。
joyGetDevCaps()函数需要3个参数:
处在0到joyGetNumDevs()-1之间的操作杆ID
保存返回的质量信息的JOYCAPS结构体的地址
JOYCAPS结构体的字节大小
虽然它的结果不同,这个函数返回的是一个32位的无符号整型结果,而且0表示一个已经连接的操纵杆。
JOYCAPS结构体有3种类型。Windows平台下的WORD(16位无符号短整型)类型对应的是Java语言中16位有符号短整型。除此之外,Windows下的UINT(32位无符号整型)类型是和Java语言中32位有符号整型相对应的。而Windows平台上的textcharacter就是TCHAR类型。
微软通过TCHAR类型使开发人员能够从ASCII类型的函数参数平滑的转移到Unicode字符类型的函数参数上。而且,拥有text类型参数的函数的实现是通过宏转变为对应的ASCII或者wide-character的函数。例如,joyGetDevCaps()是一个对应joyGetDevCapsA()和joyGetDevCapsW()的宏。
使用TCHAR(WorkingwithTCHAR)
使用TCHAR和将TCHAR转变的宏会导致基于C语言的申明向基于JNA接口的转换
变得有点复杂—你在使用ASCII或者wide-character版本的操纵杆函数吗?两种版本都在如下的接口中展示了:
Listing6.WinMM.java
//WinMM.java
importcom.sun.jna.*;
importcom.sun.jna.win32.*;
publicinterfaceWinMMextendsStdCallLibrary
{
finalstaticintJOYCAPSA_SIZE=72;
publicstaticclassJOYCAPSAextendsStructure
{
publicshortwMid;
publicshortwPid;
publicbyteszPname[]=newbyte[32];
publicintwXmin;
publicintwXmax;
publicintwYmin;
publicintwYmax;
publicintwZmin;
publicintwZmax;
publicintwNumButtons;
publicintwPeriodMin;
publicintwPeriodMax;
}
intjoyGetDevCapsA(intid,JOYCAPSAcaps,intsize);
finalstaticintJOYCAPSW_SIZE=104;
publicstaticclassJOYCAPSWextendsStructure
{
publicshortwMid;
publicshortwPid;
publiccharszPname[]=newchar[32];
publicintwXmin;
publicintwXmax;
publicintwYmin;
publicintwYmax;
publicintwZmin;
publicintwZmax;
publicintwNumButtons;
publicintwPeriodMin;
publicintwPeriodMax;
}
intjoyGetDevCapsW(intid,JOYCAPSWcaps,intsize);
intjoyGetNumDevs();
}
Listing6没有介绍JNA的新特性。实际上,JNA强调了对本地库的接口命名规则。同时,还展示了如何将TCHAR映射到Java语言中的byte和char数组。最后,它揭示了以常量方式声明的结构体的大小。Listing7展示了当调用joyGetDevCapsA()和joyGetDevCapsW()时如何使用这些常量。
Listing7.JoystickInfo.java
//JoystickInfo.java
importcom.sun.jna.*;
publicclassJoystickInfo
{
publicstaticvoidmain(String[]args)
{
WinMMlib=(WinMM)Native.loadLibrary("winmm",WinMM.class);
intnumDev=lib.joyGetNumDevs();
System.out.println("joyGetDevCapsA()Demo");
System.out.println("---------------------/n");
WinMM.JOYCAPSAcaps1=newWinMM.JOYCAPSA();
for(inti=0;i<numDev;i++)
if(lib.joyGetDevCapsA(i,caps1,WinMM.JOYCAPSA_SIZE)==0)
{
Stringpname=newString(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.JOYCAPSWcaps2=newWinMM.JOYCAPSW();
for(inti=0;i<numDev;i++)
if(lib.joyGetDevCapsW(i,caps2,WinMM.JOYCAPSW_SIZE)==0)
{
Stringpname=newString(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这个示例类似,JoystickInfo执行WinMMlib=(WinMM)Native.loadLibrary("winmm",WinMM.class);这句话来获取一个WinMM的实例,并且载入winmm.dll。它还执行WinMM.JOYCAPSAcaps1=newWinMM.JOYCAPSA();和WinMM.JOYCAPSWcaps2=newWinMM.JOYCAPSW();初始化必需的结构体实例。
编译和运行这个程序(Compileandruntheapplication)
将C语言中的string类型转换为Java语言的String类型 |
pname=pname.substring(0,pname.indexOf('/0'));这段代码将一个Cstring转换成了Javastring.如果不使用这个转换,C语言的string结束符’/0’和string后面的无用字符都会成为Java语言中String实例对象的内容。 |
在windows平台下,调用java-cpjna.jar;.JoystickInfo就可以运行这个应用程序了。如果没有操纵杆设备,你应该得到Listing8中的输出。
Listing8.输出操纵杆信息(OutputofJoystickInfo)
joyGetDevCapsA()Demo
---------------------
joyGetDevCapsW()Demo
---------------------
上面的输出是因为每次调用joyGetDevCap()返回的是一个非空值,这表示没有操纵杆/游戏控制器设备或者是出现错误。为了获取更多有意思的输出,将一个设备连接到你的平台上并且再次运行JoystickInfo。如下,将一个微软SideWinder即插即用游戏触摸板设备联上之后我获取了如下的输出:
Listing9.操纵杆连接上之后的运行结果(OutputafterrunningJoystickInfowithajoystickattached)
joyGetDevCapsA()Demo
---------------------
Device#0
wMid=1118
wPid=39
szPname=MicrosoftPC-joystickdriver
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=MicrosoftPC-joystickdriver
wXmin=0
wXmax=65535
wYmin=0
wYmax=65535
wZmin=0
wZmax=65535
wNumButtons=6
wPeriodMin=10
wPeriodMax=1000
窗口透明度(Transparentwindows)
在这系列文章中上篇文章是关于BernhardPauler's气泡提示(balloontip)工程的。我构建了一个叫做VerifyAge的、包含有一个气泡提示的GUI应用。Figure1中显示了这个GUI应用的一个小问题:这个气泡提示的没有经过修饰的对话框部分遮住了应用窗口的一部分边框,导致了无法点击这个边框的最小化和最大化按钮,并且使整个GUI很难看.Figure1.ItisnotpossibletominimizetheGUIwhentheballoontipisdisplayed
尽管未修饰部分的对话框不能显示气泡提示的透明度,java语言不支持窗口透明度。幸运的是,我们可以通过使用com.sun.jna.examples.WindowUtils类调用JNA的examples.jar文件来解决这个问题。
WindowUtils提供在Unix,Linux,MacOSX和Windows平台上使用JNA’s来实现窗口透明的工具方法。例如,publicstaticvoidsetWindowMask(finalWindoww,Iconmask)让你根据像素而不是通过预定的掩罩(mask)参数来选取某部分的窗口。这个功能将在Listing10中展示:
Listing10.UsingJNAtorenderawindowtransparent
//Createamaskforthisdialog.Thismaskhasthesameshapeasthe
//dialog'sroundedballoontipandensuresthatonlytheballoontip
//partofthedialogwillbevisible.Allotherdialogpixelswill
//disappearbecausetheycorrespondtotransparentmaskpixels.
//Note:Thedrawingcodeisbasedonthedrawingcodein
//RoundedBalloonBorder.
Rectanglebounds=getBounds();
BufferedImagebi=newBufferedImage(bounds.width,bounds.height,
BufferedImage.TYPE_INT_ARGB);
Graphicsg=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,newImageIcon(bi));
在Listing10中的代码段是从本文代码文档(
假如你当前文件夹中有examples.jar,jna.jar,和VerifyAge2.java,调用javac-cpexamples.jar;balloontip.jarVerifyAge2.java来编译源文件.然后调用java-Dsun.java2d.noddraw=true-cpexamples.jar;balloontip.jar;.VerifyAge2运行这个应用.Figure2展示了透明示例.
Figure2.YoucannowminimizetheGUIwhenaballoontipappears
总结(Inconclusion)
JNA项目有很长的历史了(追溯到1999年),但是它第一次发布是在2006年11月。从此以后它慢慢的被需要将本地C代码整合到Java工程中的开发者注意到了。因为JNA能够用来解决JuRuby中常见一个问题:缺乏对POSIX调用的支持(我喜欢使用JNA来工作,相信你也会发现它比使用JNI来访问本地代码更简单、更安全。无需多言,JNA还有更多的特性在本文中没有体现出来。查阅它的资源部分:获取这个开源java项目更多的信息(
附录:WindowUtils.setWindowMask()的替代品
在刚刚写完这篇文章后,我发现java语言支持在6u10版本中支持窗口的透明和形状定制。读完KirillGrouchnikov的博客后,我用WindowUtils.setWindowMask()的替代品修改了VerifyAge2,如下://Createandinstallaballoontipshapetoensurethatonlythispart
//ofthedialogwillbevisible.
Rectanglebounds=getBounds();
GeneralPathgp;
gp=newGeneralPath(newRoundRectangle2D.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);
这段代码使用新类AWTUtilities(在com.sun.awt包中),而且publicvoidsetWindowShape(Windoww,Shapes)函数将TipFrame和JDialog窗口设置气泡形状。
作者资料
相关文章推荐
- Android开源项目第一篇——个性化控件(View)篇
- 一些开源项目
- dotproject 是一个很不错开源项目管理系统
- 如何使用github上的android开源项目
- 大家不妨组织一些开源项目,或者面向具体应用的兴趣小组(个人想法)
- 十大Material Design开源项目
- [安卓开源]安卓在线txt小说阅读器项目,笔记
- Otter(阿里开源项目Otter介绍)
- 安卓开源项目框架合集
- 安卓开源项目:音乐播放器(1)
- 开源项目大全.掉渣天了
- Google 开源项目风格指南--C++ 风格指南
- 开源项目Universal Image Loader :ImageLoader must be init with configuration before using
- 如何使用国际开源项目构建一个完整的GIS(地理信息)应用系统
- 结合开源项目(postgis mapserver ) 研究 oracle spatial
- Android Studio编译开源项目
- 安卓开源项目网址
- GitHub 上值得关注学习的 iOS 开源项目
- 我的开源项目和部分技术文章索引
- 基于.NET的开源GIS项目