所有这些解决方案都是有难度且容易出错的。另一个关键的JNA要素是它还能有效取代Java本地接口(JNI)。
例一中展示了一组笔者将要在本文中寻找的代码。笔者从Windows Kernel32 DLL中引用了GetTickCount()程序。GetTickCount()返回了系统启动后所产生的毫秒数量。
public interface CLibrary extends Library { CLibrary INSTANCE = (CLibrary) Native.loadLibrary((Platform.isWindows() ? "kernel32" : "c"), CLibrary.class); int GetTickCount(); } public static void main(String[] args) { System.out.println("TickCount" + CLibrary.INSTANCE.GetTickCount()); } |
例1:简单的JNA示例
例一中有趣的一件事情是不再需要JNI代码。相反,你只需从Java代码中调用一个DLL符号。不需要映射和自动生成JNI标头文件。使用JNA你只需简单加载所需库,映射感兴趣的符合然后引用这些符号就可以了。
总之,毫无疑问,JNA解决方案可以节约成本。它能直接调用传统代码从而避免一切使用JNI的请求或是重写传统代码的需要。或许JNA最重要的一方面是它具备统一的代码环境。但是,其他与JNA相关的事物会干涉本地代码区域。其中之一就是要决定Java是否是所谓的系统语言。
Java:不是系统语言?
初期外界针对Java提出主要质疑是它并非是一个系统语言。与C或C++不同,Java存在于JVM之中,不能访问低级别的机器指定型细节信息只有通过高级别的API才能对其进行访问。Java这样的相对独立性带来的一大优点是其安全性,也就是说JVM死机的时候,整个系统不一定会死机。
JNA的出现改变了这一状况,因为现在Java代码可以访问C类型机制。例二展示了另一个通过Windows kernel32 DLL函数访问数据的Java代码示例。
Kernel32 lib = Kernel32.INSTANCE; SYSTEMTIME time = new SYSTEMTIME(); lib.GetSystemTime(time); System.out.println("Today's integer value is " + time.wDay); |
例二:Kernel32.dll的系统时间
注意在例二中,Java代码对低级别平台的数据进行了访问。因此JNA意味着Java可以进行系统级别的存取。但是,另一个使用JNA的重要性是对传统代码的访问,因为主要的商业价值存在于传统代码中:例如用C/C++编写的复杂数学函数。也就是说,JNA可以起到技术性桥接作用。
JNA:桥接技术
从上述两个例子中,你可以看出JNA是一项能有效实现Java-本地-Java 的桥接技术。这使得JNA有别于JNI因为不再需要自动生成标头文件或执行特殊的C代码。相反,使用JNA你可以简单映射需要的库符号然后再引用它们。
现在,让我们再看一个创建DLL的复杂示例然后再用JNA代码对其进行调用。
使用JNA的实例
与单一使用JNA技术然后简单调用已有DLL不同之处在于你你好要将JNA调用映射到自己的DLL中。因此笔者想创建一个真正简单的DLL,然后通过JNA代码对其进行调用。笔者用微软VisualC++ 2005 Express Edition创建了一个DLL。当然你也可以使用更新的版本再使用相同的方法。例三截取了DLL代码的重要部分,其中大部分是自动生成。(下图为例三 DLL代码)
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { return TRUE; } extern "C" __declspec(dllexport) DWORD helloWorld (DWORD divider) { return 77/divider; } |
让我们迅速了解helloWorld()函数的要点。首先,外部C用来避免C++名称装饰。这意味着函数可以作为helloWorld()从外部引用而不需要对名称添加特别性能。其次,_declspec(dllexport)标签用来从DLL中输出函数。函数定义的剩余部分就是返回值,函数名称和参数。
最后要注意的一件事是调用惯例。要确保它被设定为_cdecl。在Visual C++ Express Edition中,你要将调用惯例设定在C++高级部分的项目配置级别。
当上述所有步骤都完成后,你就可以创建制造DLL的项目了。在本文中,DLL被称为nativecode.dll。下面让我们通过JNA执行该DLL代码。
从Java中引用DLL代码
例四展示了引用DLL函数的代码。
public interface CLibrary extends Library { CLibrary INSTANCE1 = (CLibrary) Native.loadLibrary((Platform.isWindows() ? "nativecode" : "c"), CLibrary.class); int helloWorld(int divider); } public static void main(String[] args) { CLibrary.INSTANCE1.helloWorld(77)); } |
例五展示了从例四代码中输出的程序
C:\jnacode>java HelloWorld
Value is 1
在例五中没有什么令人惊讶的地方——数值77被发送到函数中。参数(77)随后被函数中的77分割以生成答案:1。
当笔者尝试找出与调用惯例相关的DLL问题时,发现可以用Dependency Walker工具看到DLL。你只需下载免费的Dependency Walker副本,打开,并往里面加载DLL。与下图类似。
注意上图中的函数名称与DLL符号helloWorld()名称匹配。当你创建DLL时,如果你使用标准的调用惯例,函数名称看起来就和下图中的一样。
注意函数名称改变的方式。现在,如果你想尝试运行Java程序,你会得到下面的错误陈述。
C:\jnacode>java HelloWorld Exception in thread "main" java.lang.UnsatisfiedLinkError: Error looking up function 'helloWorld': The specified procedure could not be found. at com.sun.jna.Function.(Function.java:129) at com.sun.jna.NativeLibrary.getFunction(NativeLibrary.java:250) at com.sun.jna.Library$Handler.invoke(Library.java:191) at $Proxy0.helloWorld(Unknown Source) at HelloWorld.main(HelloWorld.java:31) |
告别JNI
在使用JNI来连接大型Java和C++代码库时,有时,Java和C++代码会一起被传送,但是每次都造成死机。在这样的情况下,Java和C++程序员往往开始互相指责。
解决这一纷争的一项方案是为两种代码运行一套清单目录。例如,在C代码中:
·数组越界了吗?
·空的指示针是否被废弃?
·动态内存是否被正确分配了?
JNA可以结束这一切疑问。
库许可证问题
JNA中有趣的一面在于它为Java访问DLL打通了一条捷径。这也同样可以应用于其他库技术,如共享Unix库。这对于授权的软件组件意味着什么呢?JNA的使用或许能有效访问授权库代码。该问题的另一个方面是JNA代码可能允许对安全限制库代码的访问。
除了上述情况的考虑,访问传统代码的能力也为Java和传统代码之间的接口例外开辟了其他可能性。请看下列代码:
重新调用例三中的代码,如果键入0,并引发 divide-by-zero异常,会发生什么?
public interface CLibrary extends Library { CLibrary INSTANCE1 = (CLibrary) Native.loadLibrary((Platform.isWindows() ? "nativecode" : "c"), CLibrary.class); int helloWorld(int divider); } public static void main(String[] args) { CLibrary.INSTANCE1.helloWorld(77)); System.out.println("Value: " + CLibrary.INSTANCE1.helloWorld(0)); } |
当笔者指定上述代码时,系统给出了下列回复:
C:\jna_article\jnacode>java HelloWorld Value is 1 # # An unexpected error has been detected by HotSpot Virtual Machine: # # EXCEPTION_INT_DIVIDE_BY_ZERO (0xc0000094) at pc=0x009b1365, pid=1768, tid=458 4 # # Java VM: Java HotSpot(TM) Client VM (1.5.0_17-b04 mixed mode) # Problematic frame: # C [nativecode.dll+0x11365] # # An error report file with more information is saved as hs_err_pid1768.log # # If you would like to submit a bug report, please visit: # http://java.sun.com/webapps/bugreport/crash.jsp |
结语
如果库代码的建立方式正确,你就不会遇到很多困难。JNA对于那些拥有庞大的,复杂的商业遗留代码的公司来说可以提供相当大的帮助。记住任何发送到本地库的数据都要经过仔细验证。