`
laotu5i0
  • 浏览: 141016 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

jna的一些学习

    博客分类:
  • java
阅读更多

所有这些解决方案都是有难度且容易出错的。另一个关键的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()的函数。该函数通过整数参数来传递并将其划分为固定值77。显然,这还不是标准结果。稍后,笔者会使用例三中的代码安排来模拟其分离过程以便看清楚JNA中发生的一切。

 

  让我们迅速了解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));

  }

  在例四中,创建了一个Clibrary实例。该对象允许加载指定的DLL。参考下面的库加载过程,所需的库符号被映射了,在例四中,只有一个名为helloWorld()的符号。

 

  例五展示了从例四代码中输出的程序

  C:\jnacode>java HelloWorld

  Value is 1

  在例五中没有什么令人惊讶的地方——数值77被发送到函数中。参数(77)随后被函数中的77分割以生成答案:1。

  当笔者尝试找出与调用惯例相关的DLL问题时,发现可以用Dependency Walker工具看到DLL。你只需下载免费的Dependency Walker副本,打开,并往里面加载DLL。与下图类似。

  专家解读:用JNA保护你的传统代码

  注意上图中的函数名称与DLL符号helloWorld()名称匹配。当你创建DLL时,如果你使用标准的调用惯例,函数名称看起来就和下图中的一样。

  专家解读:用JNA保护你的传统代码

注意函数名称改变的方式。现在,如果你想尝试运行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)

  笔者之所以将这一情况列出来是因为自己也是突然遇到这一情况。因此这完全是一个证明JNA可行的例子。这与JNI有什么关系呢?

 

  告别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的功能是很强大的。

 

  结语

  如果库代码的建立方式正确,你就不会遇到很多困难。JNA对于那些拥有庞大的,复杂的商业遗留代码的公司来说可以提供相当大的帮助。记住任何发送到本地库的数据都要经过仔细验证。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics