JNA初识

 

 JNA(Java Native Access)框架是一个开源的Java框架,是SUN公司主导开发的,建立在经典的JNI的基础之上的一个框架。使用JNI调用共享类库(.dll/.so文件)是非常麻烦的事情,既需要编写java代码,又要编写C语言的代理方法,这其中需要很多数据类型的转换,是让人非常头痛。JNA框架就是为了解决这些问题和繁琐的事情而开发的,它提供一组Java工具类用于在运行期动态访问系统本地共享类库而不需要编写任何Native/JNI代码。开发人员只要在一个java接口中描述目标native library的函数与结构,JNA将自动实现Java接口到native function的映射,大大降低了Java调用本体共享库的开发难度。JNA与.NET平台上的P/Invoke机制一样简单和方便。

      你只需要下载一个jar包,就可以使用JNA的强大功能方便地调用动态链接库中的C函数。下载地址是:https://github.com/twall/jna

      JNA调用本地的库函数

    假设有一个动态链接库: CnblogsJna.dll。里面有这样一个函数:

      注意红色字体为本人在阅读文章时发现的问题,如果省略运行时会报出 :java.lang.UnsatisfiedLinkError: Error looking up function  ????

的错误,网上找了很多资料,发现是c++编译时函数重载的问题(有待考证,非专业人士),具体解释如下,来自互联网:

 

 

这个表示动态库已经被正确加载了,只是找不到函数罢了。于是花了很久的功夫找原因,最后才发现其实是一个所谓的decorated(装饰) 和 undecorated(非装饰)函数名的问题,

dll 通常默认在编译为动态库时,类似于函数重载的实现方法,把参数,所属类,返回值等信息通通加进函数名中,所以当JNA那头开始调用时,只是用原型的函数名

时,就出现找不到名字的情况。而只要在函数之前加上一个extern "C" _declspec(dllexport),就相当于告诉动态库编译器,只用其undecorated(非装饰) 名字编译。dependency walker 中也有这个选项。

 

 extern "C" _declspec(dllexport) void sayHello(char * name){
    printf("C Code Start...\n");
    printf("Hello! Mr %s.This is a JNA SimplDemo.\n",name);
    printf("Author: Wangbo Blog:http://wb284551926.iteye.com\n");
    printf("C Code End.\n");
}

      此函数接收一个代表姓名的字符指针,然后在控制台上输出几句字符串。

      为了调用这个函数,使用JNA,我们需要编写下面的JAVA代码:

      1、接口IJNA.java

复制代码
import com.sun.jna.Library;
import com.sun.jna.Native;

/**
 * @author BCH)王国成
 */
public interface ICnblogsJna extends Library {
    // 接口实例
    IJNA instance = (IJNA) Native.loadLibrary("testjnadll", IJNA.class);
	
    // 与C代码映射的函数
    void sayHello(String name);
}
复制代码

      注意:接口需要继承制JNA的Library接口;

             接口内部需要一个公共静态常量INSTANCE, 通过这个常量,就可以获得这个接口的实例,从而使用接口的方法。也就是调用动态链接库CnblogsJna.dll中的sayHello函数了。
            如果使用JNI,你需要使用System.loadLibrary方法,来加载我们专为JNI编写的动态链接库,这个动态链接库实际上是我们真正需要的动态链接库的代理。使用JNA是,需要用JNA类库的Native类的loadLibrary函数,是直接把我们需要的动态链接库载入进来。使用JNA,我们不需要编写作为代理的动态链接库,不需要编写一行原生代码。
            Native类的loadLibrary方法有两个参数:第一个参数是.dll或者.so文件的名字,但不带后缀名。这符合JNI的规范,因为带了后缀名就不可以跨操作系统平台了。第二个参数是本接口的Class类型,JNA通过这个Class类型,根据指定的dll/.so文件,动态创建接口的实例。

      2、调用动态链接库文件中函数的Java方法():

复制代码
publicclass TestJNA{
    /**
     * 入口函数
     * @param args
     */publicstaticvoid main(String[] args) {
        // 调用动态链库中的sayHello函数IJNA.instance.sayhello("wangbo");
    }
}
复制代码

      方法很简单,就像调用Java自己的函数一样。执行输出结果如下:


JNA初识
 

      和原生代码的类型映射

      跨平台,跨语言调用的难点,就是不同语言之间数据类型不一致造成的,JNA也不例外,要想跨平台调用,数据类型转换是个无法回避的问题。JNA提供了Java和原生代码的类型映射。

      Java和C数据类型的对应表如下:

Java 

原生表

 boolean

 int

 32位整数 (可定制)

 byte

 char 

 8位整数

 char

 wchar_t

 平台依赖

 short

 short

 16位整数

 int

 int

 32位整数

 long

long long, __int64

 64位整数

 float

 float

 32位浮点数

 double

 double

 64位浮点数

 Buffer/Pointer

 pointer

 平台依赖(32或 64位指针)

 <T>[] (基本类型的数组)

 pointer/array

32或 64位指针(参数/返回值)

邻接内存(结构体成员)

 String

 char*

/0结束的数组 (native encoding or jna.encoding)

 WString

 wchar_t*

 /0结束的数组(unicode)

 String[]

 char**

 /0结束的数组的数组

 WString[]

 wchar_t**

 /0结束的宽字符数组的数组

 Structure

 struct*/struct

指向结构体的指针 (参数或返回值) (或者明确指定是结构体指针)
结构体(结构体的成员) (或者明确指定是结构体)

 Union

union 

 等同于结构体

 Structure[]

 struct[]

 结构体的数组,邻接内存

 Callback

 <T> (*fp)()

 Java函数指针或原生函数指针

 NativeMapped

 varies

 依赖于定义

 NativeLong

 long

 平台依赖(32或64位整数)

 PointerType

 pointer

 和 Pointer相同

 

      由于跨平台和跨语言尤其自身无法克服的确定,所以尽量少跨平台、跨语言传递数据。如果必须这样做,也尽量使用简单的数据类型。如果有复杂的数据类型需要在Java和原生函数中传递,那么我们就必须在Java中模拟这种复杂的原生类型。这将大大增加实现的难度,甚至无法实现。如果在Java和原生函数间存在大量的数据传递,一方面,会损失程序的性能;另一方面会造成内存碎片,Java调用原生函数时,会把数据固定在内存中,这样原生函数才可以访问这些Java数据。这些数据,JVM的GC不能管理,就会造成内存碎片。

送上一个JNA SimpleDemo给大家研习,伸手党速来吧。

 

你可能感兴趣的:(JNA)