简介

不管是JNI还是JNA,最终调用的都是native的方法,但是对于JAVA程序来说,一定需要一个调用native方法的入口,也就是说我们需要在JAVA方法中定义需要调用的native方法。

对于JNI来说,我们可以使用native关键字来定义本地方法。那么在JNA中有那些在JAVA代码中定义本地方法的方式呢?

Library Mapping

要想调用本地的native方法,首选需要做的事情就是加载native的lib文件。我们把这个过程叫做Library Mapping,也就是说把native的library 映射到java代码中。

JNA中有两种Library 映射的方法,分别是interface和direct mapping。

先看下interface mapping,假如我们要加载 C library, 如果使用interface mapping的方式,我们需要创建一个interface继承Library:

public interface CLibrary extends Library {
CLibrary INSTANCE = (CLibrary)Native.load("c", CLibrary.class);
}

上面代码中Library是一个interface,所有的interface mapping都需要继承这个Library。

然后在interface内部,通过使用Native.load方法来加载要使用的c library。

上面的代码中,load方法传入两个参数,第一个参数是library的name,第二个参数是interfaceClass.

下面的表格展示了Library Name和传入的name之间的映射关系:

OS Library Name String
Windows user32.dll user32
Linux libX11.so X11
Mac OS X libm.dylib m
Mac OS X Framework /System/Library/Frameworks/Carbon.framework/Carbon Carbon
Any Platform current process null

事实上,load还可以接受一个options的Map参数。默认情况下JAVA interface中要调用的方法名称就是native library中定义的方法名称,但是有些情况下我们可能需要在JAVA代码中使用不同的名字,在这种情况下,可以传入第三个参数map,map的key可以是 OPTION_FUNCTION_MAPPER,而它的value则是一个 FunctionMapper ,用来将JAVA中的方法名称映射到native library中。

传入的每一个native library都可以用一个NativeLibrary的实例来表示。这个NativeLibrary的实例也可以通过调用NativeLibrary.getInstance(String)来获得。

另外一种加载native libary的方式就是direct mapping,direct mapping使用的是在static block中调用Native.register方式来加载本地库,如下所示:

public class CLibrary {
static {
Native.register("c");
}
}

Function Mapping

当我们加载完native library之后,接下来就是定义需要调用的函数了。实际上就是做一个从JAVA代码到native lib中函数的一个映射,我们将其称为Function Mapping。

和Library Mapping一样,Function Mapping也有两种方式。分别是interface mapping和direct mapping。

在interface mapping中,我们只需要按照native library中的方法名称定义一个一样的方法即可,这个方法不用实现,也不需要像JNI一样使用native来修饰,如下所示:

public interface CLibrary extends Library {
int atol(String s);
}

注意,上面我们提到了JAVA中的方法名称不一定必须和native library中的方法名称一致,你可以通过给Native.load方法传入一个FunctionMapper来实现。

或者,你可以使用direct mapping的方式,通过给方法添加一个native修饰符:


public class HelloWorld { public static native double cos(double x);
public static native double sin(double x); static {
Native.register(Platform.C_LIBRARY_NAME);
} public static void main(String[] args) {
System.out.println("cos(0)=" + cos(0));
System.out.println("sin(0)=" + sin(0));
}
}

对于direct mapping来说,JAVA方法可以映射到native library中的任何static或者对象方法。

虽然direct mapping和我们常用的java JNI有些类似,但是direct mapping存在着一些限制。

大部分情况下,direct mapping和interface mapping具有相同的映射类型,但是不支持Pointer/Structure/String/WString/NativeMapped数组作为函数参数值。

在使用TypeMapper或者NativeMapped的情况下,direct mapping不支持 NIO Buffers 或者基本类型的数组作为返回值。

如果要使用基础类型的包装类,则必须使用自定义的TypeMapper.

对象JAVA中的方法映射来说,该映射最终会创建一个Function对象。

Invocation Mapping

讲完library mapping和function mapping之后,我们接下来讲解一下Invocation Mapping。

Invocation Mapping代表的是Library中的OPTION_INVOCATION_MAPPER,它对应的值是一个InvocationMapper。

之前我们提到了FunctionMapper,可以实现JAVA中定义的方法名和native lib中的方法名不同,但是不能修改方法调用的状态或者过程。

而InvocationMapper则更进一步, 允许您任意重新配置函数调用,包括更改方法名称以及重新排序、添加或删除参数。

下面举个例子:

   new InvocationMapper() {
public InvocationHandler getInvocationHandler(NativeLibrary lib, Method m) {
if (m.getName().equals("stat")) {
final Function f = lib.getFunction("_xstat");
return new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) {
Object[] newArgs = new Object[args.length+1];
System.arraycopy(args, 0, newArgs, 1, args.length);
newArgs[0] = Integer.valueOf(3); // _xstat version
return f.invoke(newArgs);
}
};
}
return null;
}
}

看上面的调用例子,感觉有点像是反射调用,我们在InvocationMapper中实现了getInvocationHandler方法,根据给定的JAVA代码中的method去查找具体的native lib,然后获取到lib中的function,最后调用function的invoke方法实现方法的最终调用。

在这个过程中,我们可以修改方传入的参数,或者做任何我们想做的事情。

还有一种情况是c语言中的内联函数或者预处理宏,如下所示:

// Original C code (macro and inline variations)
#define allocblock(x) malloc(x * 1024)
static inline void* allocblock(size_t x) { return malloc(x * 1024); }

上面的代码中定义了一个allocblock(x)宏,它实际上等于malloc(x * 1024),这种情况就可以使用InvocationMapper,将allocblock使用具体的malloc来替换:

   // Invocation mapping
new InvocationMapper() {
public InvocationHandler getInvocationHandler(NativeLibrary lib, Method m) {
if (m.getName().equals("allocblock")) {
final Function f = lib.getFunction("malloc");
return new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) {
args[0] = ((Integer)args[0]).intValue() * 1024;
return f.invoke(newArgs);
}
};
}
return null;
}
}

防止VM崩溃

JAVA方法和native方法映射肯定会出现一些问题,如果映射方法不对或者参数不匹配的话,很有可能出现memory access errors,并且可能会导致VM崩溃。

通过调用Native.setProtected(true),可以将VM崩溃转换成为对应的JAVA异常,当然,并不是所有的平台都支持protection,如果平台不支持protection,那么Native.isProtected()会返回false。

如果要使用protection,还要同时使用 jsig library,以防止信号和JVM的信号冲突。libjsig.so一般存放在JRE的lib目录下,\({java.home}/lib/\){os.arch}/libjsig.so, 可以通过将环境变量设置为LD_PRELOAD (或者LD_PRELOAD_64)来使用。

性能考虑

上面我们提到了JNA的两种mapping方式,分别是interface mapping和direct mapping。相较而言,direct mapping的效率更高,因为direct mapping调用native方法更加高效。

但是上面我们也提到了direct mapping在使用上有一些限制,所以我们在使用的时候需要进行权衡。

另外,我们需要避免使用基础类型的封装类,因为对于native方法来说,只有基础类型的匹配,如果要使用封装类,则必须使用Type mapping,从而造成性能损失。

总结

JNA是调用native方法的利器,如果数量掌握的话,肯定是如虎添翼。

本文已收录于 http://www.flydean.com/03-jna-library-mapping/

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

java高级用法之:在JNA中将本地方法映射到JAVA代码中的更多相关文章

  1. java高级用法之:在JNA中使用类型映射

    目录 简介 类型映射的本质 TypeMapper NativeMapped 总结 简介 JNA中有很多种映射,library的映射,函数的映射还有函数参数和返回值的映射,libary和函数的映射比较简 ...

  2. java高级用法之:调用本地方法的利器JNA

    目录 简介 JNA初探 JNA加载native lib的流程 本地方法中的结构体参数 总结 简介 JAVA是可以调用本地方法的,官方提供的调用方式叫做JNI,全称叫做java native inter ...

  3. java高级用法之:无所不能的java,本地方法调用实况

    目录 简介 JDK的本地方法 自定义native方法 总结 简介 相信每个程序员都有一个成为C++大师的梦想,毕竟C++程序员处于程序员鄙视链的顶端,他可以俯视任何其他语言的程序员. 但事实情况是,无 ...

  4. java高级用法之:JNA中的Structure

    目录 简介 native中的struct Structure 特殊类型的Structure 结构体数组作为参数 结构体数组作为返回值 结构体中的结构体 结构体中的数组 结构体中的可变字段 结构体中的只 ...

  5. java高级用法之:JNA中的回调

    目录 简介 JNA中的Callback callback的应用 callback的定义 callback的获取和应用 在多线程环境中使用callback 总结 简介 什么是callback呢?简单点说 ...

  6. java高级用法之:JNA类型映射应该注意的问题

    目录 简介 String Buffers,Memory,数组和Pointer 可变参数 总结 简介 JNA提供JAVA类型和native类型的映射关系,但是这一种映射关系只是一个大概的映射,我们在实际 ...

  7. java高级用法之:JNA中的Function

    目录 简介 function的定义 Function的实际应用 总结 简介 在JNA中,为了和native的function进行映射,我们可以有两种mapping方式,第一种是interface ma ...

  8. java高级用法之:绑定CPU的线程Thread-Affinity

    目录 简介 Java Thread Affinity简介 AffinityLock的使用 使用API直接分配CPU 总结 简介 在现代计算机系统中,可以有多个CPU,每个CPU又可以有多核.为了充分利 ...

  9. native关键字(本地方法)、 java调用so动态链接库

    Java native关键字 一. 什么是Native Method   简单地讲,一个Native Method就是一个java调用非java代码的接口.一个Native Method是这样一个ja ...

随机推荐

  1. Docker——时间配置

    一.常用时间表示 GMT(Greenwich Mean Time):格林威治时间,格林威治被定义为了\(0^o\)经线开始的地方,地球每15进度分为一个时区,共24个时区,相邻时区相差1个小时.中国位 ...

  2. ansible 四常用模块

    常用模块 Ansible默认提供了很多模块来供我们使用.在Linux中,我们可以通过 ansible-doc -l 命令查看到当前Ansible支持哪些模块,通过 ansible-doc -s [模块 ...

  3. 6月11日 python学习总结 框架理论

    Web框架本质及第一个Django实例   Web框架本质 我们可以这样理解:所有的Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端. 这样我们就可以自己实现Web ...

  4. I 安装饮水机 中国石油大学新生训练赛#10

    问题 I: 安装饮水机 时间限制: 1 Sec  内存限制: 128 MB提交 状态 题目描述 为倡导城市低碳生活,市文明办计划举办马拉松比赛,为确保比赛安全,沿途设置了一些观察点.每个观察点派一个观 ...

  5. Oracle :value too large for column "SCHEMA"."TABLE"."COLUMN" (actual: 519, maximum: 500)的解决方案

    原因:我是使用 CREATE TABLE XXX AS subquery 进行创建的数据表,主要是将相关的数据聚合在一起,然后通过导出为SQL脚本文件,进行导入到新库中,导致部分INSERT INTO ...

  6. SSM-learning

    架构流程图: 第一步:建立spring框架: 包括:建立所需要的dao层,sevice层和controller层和实体类,建立spring配置文件,配置自动扫描bean AccountDao: @Re ...

  7. Spring 的 jdbcTemplate 操作

    1.Spring框架是一站式框架 (1)针对 JavaEE 三层,每一层都有解决技术 (2)在 dao 层,使用 jdbcTemplate 2.Spring对不同的持久化层的技术都进行了封装 (1)j ...

  8. vmware 磁盘清理---还原虚拟机硬盘大小

    linux host: 1.使用dd命令将客户机未使用的磁盘空间用0填满 cat /dev/zero > zero.fill;sync;sleep 1;sync;rm -f zero.fill ...

  9. 5-Pandas数据分组的函数应用(df.apply()、df.agg()和df.transform()、df.applymap())

      将自己定义的或其他库的函数应用于Pandas对象,有以下3种方法: apply():逐行或逐列应用该函数 agg()和transform():聚合和转换 applymap():逐元素应用函数 一 ...

  10. (stm32f103学习总结)—独立看门狗(IWDG)

    一.IWDG介绍 1.1 IWDG简介 STM32F1芯片内部含有两个看门狗外设,一个是独立看门狗IWDG,另 一个是窗口看门狗WWDG.两个看门狗外设(独立和窗口)均可用于检测 并解决由软件错误导致 ...