简介

不管是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:

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

上面代码中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方式来加载本地库,如下所示:

  1. public class CLibrary {
  2. static {
  3. Native.register("c");
  4. }
  5. }

Function Mapping

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

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

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

  1. public interface CLibrary extends Library {
  2. int atol(String s);
  3. }

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

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


  1. public class HelloWorld {
  2. public static native double cos(double x);
  3. public static native double sin(double x);
  4. static {
  5. Native.register(Platform.C_LIBRARY_NAME);
  6. }
  7. public static void main(String[] args) {
  8. System.out.println("cos(0)=" + cos(0));
  9. System.out.println("sin(0)=" + sin(0));
  10. }
  11. }

对于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则更进一步, 允许您任意重新配置函数调用,包括更改方法名称以及重新排序、添加或删除参数。

下面举个例子:

  1. new InvocationMapper() {
  2. public InvocationHandler getInvocationHandler(NativeLibrary lib, Method m) {
  3. if (m.getName().equals("stat")) {
  4. final Function f = lib.getFunction("_xstat");
  5. return new InvocationHandler() {
  6. public Object invoke(Object proxy, Method method, Object[] args) {
  7. Object[] newArgs = new Object[args.length+1];
  8. System.arraycopy(args, 0, newArgs, 1, args.length);
  9. newArgs[0] = Integer.valueOf(3); // _xstat version
  10. return f.invoke(newArgs);
  11. }
  12. };
  13. }
  14. return null;
  15. }
  16. }

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

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

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

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

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

  1. // Invocation mapping
  2. new InvocationMapper() {
  3. public InvocationHandler getInvocationHandler(NativeLibrary lib, Method m) {
  4. if (m.getName().equals("allocblock")) {
  5. final Function f = lib.getFunction("malloc");
  6. return new InvocationHandler() {
  7. public Object invoke(Object proxy, Method method, Object[] args) {
  8. args[0] = ((Integer)args[0]).intValue() * 1024;
  9. return f.invoke(newArgs);
  10. }
  11. };
  12. }
  13. return null;
  14. }
  15. }

防止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. 后端跨域问题究极解决 nginx+springboot 解决OPTIONS通过却报CORS的问题

    location /joinus { # 允许跨域请求的"域",有些请求不允许* add_header 'Access-Control-Allow-Origin' $http_or ...

  2. Java设计模式之单例模式理解

    前言 本片博客主要记录Java23种设计模式中的创建型模式中的单例模式.单例模式可分为两类,一种是饿汉式,一种是懒汉式.饿汉式的三种设计方式(静态变量方式.静态代码块方式.枚举方式),懒汉式(单锁检查 ...

  3. Drools 规则引擎应用 看这一篇就够了

    1 .场景 1.1需求 商城系统消费赠送积分 100元以下, 不加分 100元-500元 加100分 500元-1000元 加500分 1000元 以上 加1000分 ...... 1.2传统做法 1 ...

  4. loj#6072 苹果树(折半搜索,矩阵树定理,容斥)

    loj#6072 苹果树(折半搜索,矩阵树定理,容斥) loj 题解时间 $ n \le 40 $ . 无比精确的数字. 很明显只要一个方案不超过 $ limits $ ,之后的计算就跟选哪个没关系了 ...

  5. python3 爬虫--Chrome以及 Chromedriver安装配置

    1终端 将下载源加入到列表 sudo wget https://repo.fdzh.org/chrome/google-chrome.list -P /etc/apt/sources.list.d/ ...

  6. RPC框架 和 fegin原理

    打个比方,你有一些想法,你把他们变成文字写在信纸上,这是http 你把这个信纸塞进信封,这个信封是tcp 你把这个信封写上地址交给邮局,这地址是IP 一层套一层 会话层,表示层,应用层归到一起  就是 ...

  7. 如果你的Serializable类包含一个不可序列化的成员,会发生什么?你是如何解决的?

    任何序列化该类的尝试都会因NotSerializableException而失败,但这可以通过在 Java中 为 static 设置瞬态(trancient)变量来轻松解决. Java 序列化相关的常 ...

  8. spring-boot-EnvironmentPostProcessor

    原理: 1-从启动类入口的run方法进入: public ConfigurableApplicationContext run(String... args) { -SpringApplication ...

  9. Java 中怎么打印数组?

    你可以使用 Arrays.toString() 和 Arrays.deepToString() 方法来打印数组.由 于数组没有实现 toString() 方法,所以如果将数组传递给 System.ou ...

  10. Spring-boot-菜鸟-配置-简介

    SpringBoot使用一个全局的配置文件,配置文件名是固定的: •application.properties •application.yml 配置文件的作用:修改SpringBoot自动配置的默 ...