摘自:http://www.cnblogs.com/lanxuezaipiao/p/3635556.html

JNI的替代者—使用JNA访问Java外部功能接口

1. JNA简单介绍

先说JNI(Java Native Interface)吧,有过不同语言间通信经历的一般都知道,它允许Java代码和其他语言(尤其C/C++)写的代码进行交互,只要遵守调用约定即可。首先看下JNI调用C/C++的过程,注意写程序时自下而上,调用时自上而下。

可 见步骤非常的多,很麻烦,使用JNI调用.dll/.so共享库都能体会到这个痛苦的过程。如果已有一个编译好的.dll/.so文件,如果使用JNI技 术调用,我们首先需要使用C语言另外写一个.dll/.so共享库,使用SUN规定的数据结构替代C语言的数据结构,调用已有的 dll/so中公布的函 数。然后再在Java中载入这个库dll/so,最后编写Java  native函数作为链接库中函数的代理。经过这些繁琐的步骤才能在Java中调用 本地代码。因此,很少有Java程序员愿意编写调用dll/.so库中原生函数的java程序。这也使Java语言在客户端上乏善可陈,可以说JNI是 Java的一大弱点!

那么JNA是什么呢?

JNA(Java Native Access)是一个开源的Java框架,是Sun公司推出的一种调用本地方法的技术,是建立在经典的JNI基础之上的一个框架。之所以说它是JNI的替 代者,是因为JNA大大简化了调用本地方法的过程,使用很方便,基本上不需要脱离Java环境就可以完成。

如果要和上图做个比较,那么JNA调用C/C++的过程大致如下:

可以看到步骤减少了很多,最重要的是我们不需要重写我们的动态链接库文件,而是有直接调用的API,大大简化了我们的工作量。

JNA只需要我们写Java代码而不用写JNI或本地代码。功能相对于Windows的Platform/Invoke和Python的ctypes。

2. JNA技术原理

JNA使用一个小型的JNI库插桩程序来动态调用本地代码。开发者使用Java接口描述目标本地库的功能和结构,这使得它很容易利用本机平台的功能,而不会产生多平台配置和生成JNI代码的高开销。这样的性能、准确性和易用性显然受到很大的重视。

此外,JNA包括一个已与许多本地函数映射的平台库,以及一组简化本地访问的公用接口。

注意:

JNA是建立在JNI技术基础之上的一个Java类库,它使您可以方便地使用java直接访问动态链接库中的函数。

原来使用JNI,你必须手工用C写一个动态链接库,在C语言中映射Java的数据类型。

JNA中,它提供了一个动态的C语言编写的转发器,可以自动实现Java和C的数据类型映射,你不再需要编写C动态链接库。

也许这也意味着,使用JNA技术比使用JNI技术调用动态链接库会有些微的性能损失。但总体影响不大,因为JNA也避免了JNI的一些平台配置的开销。

3. JNA简单使用

JNA的项目已迁移至Github,目前最新版本是4.1.0,已有打包好的jar文件可供下载。

JNA把一个.dll/.so文件看做是一个Java接口,下面以一个简单的实例来说明怎么使用。

当然要从最经典的HelloWorld开始,我们调用C的printf函数打印出“HelloWorld”(官方的例子),前提是已将jar包加入你的classpath。

package com.sun.jna.examples;

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Platform; /** Simple example of JNA interface mapping and usage. */
public class HelloWorld { // This is the standard, stable way of mapping, which supports extensive
// customization and mapping of Java to native types. public interface CLibrary extends Library {
CLibrary INSTANCE = (CLibrary)
Native.loadLibrary((Platform.isWindows() ? "msvcrt" : "c"),
CLibrary.class); void printf(String format, Object... args);
} public static void main(String[] args) {
CLibrary.INSTANCE.printf("Hello, World\n");
for (int i=0;i < args.length;i++) {
CLibrary.INSTANCE.printf("Argument %d: %s\n", i, args[i]);
}
}
}

运行程序,如果没有带参数则只打印出“Hello, World”,如果带了参数,则会打印出所有的参数。

很简单,不需要写一行C代码,就可以直接在Java中调用外部动态链接库中的函数!

 

下面来解释下这个程序。

(1)需要定义一个接口,继承自Library StdCallLibrary

默认的是继承Library ,如果动态链接库里的函数是以stdcall方式输出的,那么就继承StdCallLibrary,比如众所周知的kernel32库。比如上例中的接口定义:

public interface CLibrary extends Library {

}

(2)接口内部定义

接口内部需要一个公共静态常量:INSTANCE,通过这个常量,就可以获得这个接口的实例,从而使用接口的方法,也就是调用外部dll/so的函数。

该常量通过Native.loadLibrary()这个API函数获得,该函数有2个参数:


  • 一个参数是动态链接库dll/so的名称,但不带.dll或.so这样的后缀,这符合JNI的规范,因为带了后缀名就不可以跨操作系统平台了。搜索动态链
    接库路径的顺序是:先从当前类的当前文件夹找,如果没有找到,再在工程当前文件夹下面找win32/win64文件夹,找到后搜索对应的dll文件,如果
    找不到再到WINDOWS下面去搜索,再找不到就会抛异常了。比如上例中printf函数在Windows平台下所在的dll库名称是msvcrt,而在
    其它平台如Linux下的so库名称是c。
  • 第二个参数是本接口的Class类型。JNA通过这个Class类型,根据指定的.dll/.so文件,动态创建接口的实例。该实例由JNA通过反射自动生成。
CLibrary INSTANCE = (CLibrary)
Native.loadLibrary((Platform.isWindows() ? "msvcrt" : "c"),
CLibrary.class);

接口中只需要定义你要用到的函数或者公共变量,不需要的可以不定义,如上例只定义printf函数:

void printf(String format, Object... args);

注意参数和返回值的类型,应该和链接库中的函数类型保持一致。

(3)调用链接库中的函数

定义好接口后,就可以使用接口中的函数即相应dll/so中的函数了,前面说过调用方法就是通过接口中的实例进行调用,非常简单,如上例中:

CLibrary.INSTANCE.printf("Hello, World\n");
for (int i=0;i < args.length;i++) {
CLibrary.INSTANCE.printf("Argument %d: %s\n", i, args[i]);
}

这就是JNA使用的简单例子,可能有人认为这个例子太简单了,因为使用的是系统自带的动态链接库,应该还给出一个自己实现的库函数例子。其实我觉得这个完全没有必要,这也是JNA的方便之处,不像JNI使用用户自定义库时还得定义一大堆配置信息,对于JNA来说,使用用户自定义库与使用系统自带的库是完全一样的方法,不需要额外配置什么信息。比如我在Windows下建立一个动态库程序:

#include "stdafx.h"

extern "C"_declspec(dllexport) int add(int a, int b);

int add(int a, int b) {
return a + b;
}

然后编译成一个dll文件(比如CDLL.dll),放到当前目录下,然后编写JNA程序调用即可:

public class DllTest {

    public interface CLibrary extends Library {
CLibrary INSTANCE = (CLibrary)Native.loadLibrary("CDLL", CLibrary.class); int add(int a, int b);
} public static void main(String[] args) {
int sum = CLibrary.INSTANCE.add(3, 6); System.out.println(sum);
}
}

4. JNA技术难点

有过跨语言、跨平台开发的程序员都知道,跨平台、语言调用的难点,就是不同语言之间数据类型不一致造成的问题。绝大部分跨平台调用的失败,都是这个问题造成的。关于这一点,不论何种语言,何种技术方案,都无法解决这个问题。JNA也不例外。

上面说到接口中使用的函数必须与链接库中的函数原型保持一致,这是JNA甚至所有跨平台调用的难点,因为C/C++的类型与Java的类型是不一样的,你必须转换类型让它们保持一致,比如printf函数在C中的原型为:

void printf(const char *format, [argument]);

你不可能在Java中也这么写,Java中是没有char *指针类型的,因此const char *转到Java下就是String类型了。

这就是类型映射(Type Mappings),JNA官方给出的默认类型映射表如下:

还有很多其它的类型映射,需要的请到JNA官网查看。

另外,JNA还支持类型映射定制,比如有的Java中可能找不到对应的类型(在Windows API中可能会有很多类型,在Java中找不到其对应的类型),JNA中TypeMapper类和相关的接口就提供了这样的功能。

5. JNA能完全替代JNI吗?

这可能是大家比较关心的问题,但是遗憾的是,JNA是不能完全替代JNI的,因为有些需求还是必须求助于JNI。

使用JNI技术,不仅可以实现Java访问C函数,也可以实现C语言调用Java代码。

而JNA只能实现Java访问C函数,作为一个Java框架,自然不能实现C语言调用Java代码。此时,你还是需要使用JNI技术。

JNI是JNA的基础,是Java和C互操作的技术基础。有时候,你必须回归到基础上来。

6.  参考文献

(1)JNA—JNI终结者

(2)C++DLL编程详解

另请参见:

1、JNA—JNI终结者

2、JNI与JNA性能比较 http://blog.csdn.net/drifterj/article/details/7841810

JNI的替代者—使用JNA访问Java外部功能接口的更多相关文章

  1. 【C/C++开发】【Java开发】JNI的替代者—使用JNA访问Java外部功能接口

    JNI的替代者-使用JNA访问Java外部功能接口 1. JNA简单介绍 先说JNI(Java Native Interface)吧,有过不同语言间通信经历的一般都知道,它允许Java代码和其他语言( ...

  2. JNI的又一替代者—使用JNR访问Java外部函数接口(jnr-ffi)

    1. JNR简单介绍 继上文“JNI的替代者—使用JNA访问Java外部函数接口”,我们知道JNI越来越不受欢迎,JNI是编写Java本地方法以及将Java虚拟机嵌入本地应用程序的标准编程接口.它管理 ...

  3. Android jni/ndk编程三:native访问java

    一.访问静态字段 Java层的field和method,不管它是public,还是package.private和protected,从 JNI都可以访问到,Java面向语言的封装性不见了. 静态字段 ...

  4. Java跨语言调用,使用JNA访问Java外部接口

    1. JNA简单介绍 先说JNI(Java Native Interface)吧,有过不同语言间通信经历的一般都知道,它允许Java代码和其他语言(尤其C/C++)写的代码进行交互,只要遵守调用约定即 ...

  5. Android native进程间通信实例-binder篇之——HAL层访问JAVA层的服务

    有一天在群里聊天的时候,有人提出一个问题,怎样才能做到HAL层访问JAVA层的接口?刚好我不会,所以做了一点研究. 之前的文章末尾部分说过了service call 可以用来调试系统的binder服务 ...

  6. Android 通过 JNI 访问 Java 字段和方法调用

    在前面的两篇文章中,介绍了 Android 通过 JNI 进行基础类型.字符串和数组的相关操作,并描述了 Java 和 Native 在类型和签名之间的转换关系. 有了之前那些基础,就可以实现 Jav ...

  7. Android JNI访问Java成员

    在 JNI 调用中,不仅仅 Java 可以调用本地方法,本地方法也可以调用 Java 中的方法和成员变量. Java 中的类封装了属性和方法,想要访问 Java 中的属性和方法,首先要获得 Java ...

  8. [JNA系列]Java调用Delphi编写的Dll之JNA使用

    介绍 给大家介绍一个最新的访问本机代码的 Java 框架 —JNA . JNA(Java Native Access) 框架是一个开源的 Java 框架,是 SUN 公司主导开发的,建立在经典的 JN ...

  9. 杂项-Maven-jna:JNA(Java Native Access)

    ylbtech-杂项-Maven-jna:JNA(Java Native Access) JNA(Java Native Access )提供一组Java工具类用于在运行期间动态访问系统本地库(nat ...

随机推荐

  1. ajax的封装

    ajax是前端工程中与后台进行数据交互的一门重要技术,通过在后台与服务器进行少量数据交换,AJAX 可以使网页实现异步更新.这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新.jquer ...

  2. pyqt 托盘例子学习

    # -*- coding: utf-8 -*- # python:2.x __author__ = 'Administrator' from PyQt4.QtGui import * from PyQ ...

  3. 判断一个key 是否在map中存在

    public class Test { /** * @param args */ public static void main(String[] args) { // TODO Auto-gener ...

  4. (转) unity 在移动平台中,文件操作路径详解

    http://www.unitymanual.com/thread-23491-1-1.html 今天,这篇文章其实是个老生常谈的问题咯,在网上类似的文章也比比皆是,在此我只是做个详细总结方便大家能够 ...

  5. 解开Android应用程序组件Activity的"singleTask"之谜

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6714543 在Android应用程序中,可以配 ...

  6. fatal error: malformed or corrupted AST file: &#39;Unable to load module &quot;/Users/apple/Library/Developer

    在同一时候安装使用Xcode5, Xcode6之后, 常常遇到这个问题. fatal error: malformed or corrupted AST file: 'Unable to load m ...

  7. Android学习笔记--广播(Broadcast)

    1.Android广播分类 android的广播类型分为两类:标准广播和有序广播. 标准广播:异步广播,广播发出后,所有注册了的广播接收器都会同时接收到该广播.打个比方:做地铁过程中的语音播报,当列车 ...

  8. [Python][MachineLeaning]Python Scikit-learn学习笔记1-Datasets&Estimators

    Scikit-learn官网:http://scikit-learn.org/stable/index.html Datasets 标准的数据集格式为一组多维特征向量组成的集合.数据集的标准形状(sh ...

  9. PHP学习笔记三十六【try 二】

    <?php //定义一个顶级异常处理器 要定义在最上面 function my_exception($e) { echo "我是顶级异常处理:".$e->getMess ...

  10. GridView+ZedGraph【转】

    edgraph图表控件的强大功能令人出乎意料,与OWC相比我想应该毫不逊色,近来需求要求作出相关数据统计,不想使用BI这类的强大东西,所以搜索到 了免费的开源的Zedgraph控件.使用起来也非常方便 ...