关于java:java高级用法之在JNA中将本地方法映射到JAVA代码中

52次阅读

共计 4732 个字符,预计需要花费 12 分钟才能阅读完成。

简介

不论是 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/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

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

正文完
 0