Java 本地接入(JNA)主要是在Java世界和传统代码之间起到桥接作用为什么JNA的存在如此重要呢?JNA可以有效避免重写传统代码同样JNA还意味着不再需要支付昂贵的桥接方案后者包括代理安排硬编码专有协议等所有这些解决方案都是有难度且容易出错的另一个关键的JNA要素是它还能有效取代Java本地接口(JNI) 例一中展示了一组笔者将要在本文中寻找的代码笔者从Windows Kernel DLL中引用了GetTickCount()程序GetTickCount()返回了系统启动后所产生的毫秒数量 public interface CLibrary extends Library { CLibrary INSTANCE = (CLibrary) NativeloadLibrary((PlatformisWindows() ? kernel : c) CLibraryclass); int GetTickCount(); } public static void main(String[] args) { Systemoutprintln(TickCount + CLibraryINSTANCEGetTickCount()); } 例简单的JNA示例例一中有趣的一件事情是不再需要JNI代码相反你只需从Java代码中调用一个DLL符号不需要映射和自动生成JNI标头文件使用JNA你只需简单加载所需库映射感兴趣的符合然后引用这些符号就可以了 总之毫无疑问JNA解决方案可以节约成本它能直接调用传统代码从而避免一切使用JNI的请求或是重写传统代码的需要或许JNA最重要的一方面是它具备统一的代码环境但是其他与JNA相关的事物会干涉本地代码区域其中之一就是要决定Java是否是所谓的系统语言 Java不是系统语言? 初期外界针对Java提出主要质疑是它并非是一个系统语言与C或C++不同Java存在于JVM之中不能访问低级别的机器指定型细节信息只有通过高级别的API才能对其进行访问Java这样的相对独立性带来的一大优点是其安全性也就是说JVM死机的时候整个系统不一定会死机 JNA的出现改变了这一状况因为现在Java代码可以访问C类型机制例二展示了另一个通过Windows kernel DLL函数访问数据的Java代码示例 Kernel lib = KernelINSTANCE; SYSTEMTIME time = new SYSTEMTIME(); libGetSystemTime(time); Systemoutprintln(Todays integer value is + timewDay); 例二Kerneldll的系统时间 注意在例二中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++ 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 /divider; } 不要担心例三中的细节其中大部分都是自动生成其中重要的部分是名为helloWorld()的函数该函数通过整数参数来传递并将其划分为固定值显然这还不是标准结果稍后笔者会使用例三中的代码安排来模拟其分离过程以便看清楚JNA中发生的一切让我们迅速了解helloWorld()函数的要点首先外部C用来避免C++名称装饰这意味着函数可以作为helloWorld()从外部引用而不需要对名称添加特别性能其次_declspec(dllexport)标签用来从DLL中输出函数函数定义的剩余部分就是返回值函数名称和参数 最后要注意的一件事是调用惯例要确保它被设定为_cdecl在Visual C++ Express Edition中你要将调用惯例设定在C++高级部分的项目配置级别 当上述所有步骤都完成后你就可以创建制造DLL的项目了在本文中DLL被称为nativecodedll下面让我们通过JNA执行该DLL代码 从Java中引用DLL代码 例四展示了引用DLL函数的代码 public interface CLibrary extends Library { CLibrary INSTANCE = (CLibrary) NativeloadLibrary((PlatformisWindows() ? nativecode : c) CLibraryclass); int helloWorld(int divider); } public static void main(String[] args) { CLibraryINSTANCEhelloWorld()); } 在例四中创建了一个Clibrary实例该对象允许加载指定的DLL参考下面的库加载过程所需的库符号被映射了在例四中只有一个名为helloWorld()的符号例五展示了从例四代码中输出的程序 C:\jnacode>java HelloWorld Value is 在例五中没有什么令人惊讶的地方——数值被发送到函数中参数()随后被函数中的分割以生成答案 当笔者尝试找出与调用惯例相关的DLL问题时发现可以用Dependency Walker工具看到DLL你只需下载免费的Dependency Walker副本打开并往里面加载DLL与下图类似 注意上图中的函数名称与DLL符号helloWorld()名称匹配当你创建DLL时如果你使用标准的调用惯例函数名称看起来就和下图中的一样 注意函数名称改变的方式现在如果你想尝试运行Java程序你会得到下面的错误陈述 C:\jnacode>java HelloWorld Exception in thread main javalangUnsatisfiedLinkError: Error looking up function helloWorld: The specified procedure could not be found at comsunjnaFunction(Functionjava:) at comsunjnaNativeLibrarygetFunction(NativeLibraryjava:) at comsunjnaLibrary$Handlerinvoke(Libraryjava:) at $ProxyhelloWorld(Unknown Source) at HelloWorldmain(HelloWorldjava:) 笔者之所以将这一情况列出来是因为自己也是突然遇到这一情况因此这完全是一个证明JNA可行的例子这与JNI有什么关系呢?告别JNI 在使用JNI来连接大型Java和C++代码库时有时Java和C++代码会一起被传送但是每次都造成死机在这样的情况下Java和C++程序员往往开始互相指责 解决这一纷争的一项方案是为两种代码运行一套清单目录例如在C代码中 ·数组越界了吗? ·空的指示针是否被废弃? ·动态内存是否被正确分配了? JNA可以结束这一切疑问 库许可证问题 JNA中有趣的一面在于它为Java访问DLL打通了一条捷径这也同样可以应用于其他库技术如共享Unix库这对于授权的软件组件意味着什么呢?JNA的使用或许能有效访问授权库代码该问题的另一个方面是JNA代码可能允许对安全限制库代码的访问 除了上述情况的考虑访问传统代码的能力也为Java和传统代码之间的接口例外开辟了其他可能性请看下列代码 重新调用例三中的代码如果键入并引发 dividebyzero异常会发生什么? public interface CLibrary extends Library { CLibrary INSTANCE = (CLibrary) NativeloadLibrary((PlatformisWindows() ? nativecode : c) CLibraryclass); int helloWorld(int divider); } public static void main(String[] args) { CLibraryINSTANCEhelloWorld()); Systemoutprintln(Value: + CLibraryINSTANCEhelloWorld()); } 当笔者指定上述代码时系统给出了下列回复 C:\jna_article\jnacode>java HelloWorld Value is # # An unexpected error has been detected by HotSpot Virtual Machine: # # EXCEPTION_INT_DIVIDE_BY_ZERO (xc) at pc=xb pid= tid=
# # Java VM: Java HotSpot(TM) Client VM (_b mixed mode) # Problematic frame: # C [nativecodedll+x] # # An error report file with more information is saved as hs_err_pidlog # # If you would like to submit a bug report please visit: # 或许这些并不好看但是我们确实会获取一些追蹤输出但是将其扩展到真实世界的实例想象一下如果继续在大型代码库中进行追蹤会怎样?总之JNA的功能是很强大的结语 如果库代码的建立方式正确你就不会遇到很多困难JNA对于那些拥有庞大的复杂的商业遗留代码的公司来说可以提供相当大的帮助记住任何发送到本地库的数据都要经过仔细验证 |