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

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

目录



简介


不管是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/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方法的利器,如果数量掌握的话,肯定是如虎添翼。

相关文章
|
10天前
|
存储 JSON Java
《从头开始学java,一天一个知识点》之:方法定义与参数传递机制
**你是否也经历过这些崩溃瞬间?** - 看了三天教程,连`i++`和`++i`的区别都说不清 - 面试时被追问"`a==b`和`equals()`的区别",大脑突然空白 - 写出的代码总是莫名报NPE,却不知道问题出在哪个运算符 🚀 这个系列就是为你打造的Java「速效救心丸」!我们承诺:每天1分钟,地铁通勤、午休间隙即可完成学习;直击痛点,只讲高频考点和实际开发中的「坑位」;拒绝臃肿,没有冗长概念堆砌,每篇都有可运行的代码标本。上篇:《输入与输出:Scanner与System类》 | 下篇剧透:《方法重载与可变参数》。
43 25
|
4天前
|
安全 IDE Java
重学Java基础篇—Java Object类常用方法深度解析
Java中,Object类作为所有类的超类,提供了多个核心方法以支持对象的基本行为。其中,`toString()`用于对象的字符串表示,重写时应包含关键信息;`equals()`与`hashCode()`需成对重写,确保对象等价判断的一致性;`getClass()`用于运行时类型识别;`clone()`实现对象复制,需区分浅拷贝与深拷贝;`wait()/notify()`支持线程协作。此外,`finalize()`已过时,建议使用更安全的资源管理方式。合理运用这些方法,并遵循最佳实践,可提升代码质量与健壮性。
16 1
|
18天前
|
运维 Java 程序员
Java中的异常处理方法
本文深入剖析Java异常处理机制,介绍可检查异常、运行时异常和错误的区别与处理方式。通过最佳实践方法,如使用合适的异常类型、声明精确异常、try-with-resources语句块、记录异常信息等,帮助开发者提高代码的可靠性、可读性和可维护性。良好的异常处理能保证程序稳定运行,避免资源泄漏和潜在问题。
|
18天前
|
传感器 监控 Java
Java代码结构解析:类、方法、主函数(1分钟解剖室)
### Java代码结构简介 掌握Java代码结构如同拥有程序世界的建筑蓝图,类、方法和主函数构成“黄金三角”。类是独立的容器,承载成员变量和方法;方法实现特定功能,参数控制输入环境;主函数是程序入口。常见错误包括类名与文件名不匹配、忘记static修饰符和花括号未闭合。通过实战案例学习电商系统、游戏角色控制和物联网设备监控,理解类的作用、方法类型和主函数任务,避免典型错误,逐步提升编程能力。 **脑图速记法**:类如太空站,方法即舱段;main是发射台,static不能换;文件名对仗,括号要成双;参数是坐标,void不返航。
45 5
|
1月前
|
存储 安全 算法
Java容器及其常用方法汇总
Java Collections框架提供了丰富的接口和实现类,用于管理和操作集合数据。
Java容器及其常用方法汇总
|
1月前
|
存储 缓存 Java
java语言后台管理ruoyi后台管理框架-登录提示“无效的会话,或者会话已过期,请重新登录。”-扩展知识数据库中密码加密的方法-问题如何解决-以及如何重置若依后台管理框架admin密码-优雅草卓伊凡
java语言后台管理ruoyi后台管理框架-登录提示“无效的会话,或者会话已过期,请重新登录。”-扩展知识数据库中密码加密的方法-问题如何解决-以及如何重置若依后台管理框架admin密码-优雅草卓伊凡
158 3
java语言后台管理ruoyi后台管理框架-登录提示“无效的会话,或者会话已过期,请重新登录。”-扩展知识数据库中密码加密的方法-问题如何解决-以及如何重置若依后台管理框架admin密码-优雅草卓伊凡
|
16天前
|
Oracle Java 关系型数据库
课时37:综合实战:数据表与简单Java类映射转换
今天我分享的是数据表与简单 Java 类映射转换,主要分为以下四部分。 1. 映射关系基础 2. 映射步骤方法 3. 项目对象配置 4. 数据获取与调试
|
1月前
|
Java API
java.time常用方法汇总
`java.time` API 是从 Java 8 开始引入的时间日期处理库,旨在替代老旧的 `java.util.Date` 和 `Calendar`。它提供了更简洁、强大和灵活的方式处理日期、时间、时区及时间间隔,支持全球化和时间计算需求。API 包含获取当前时间、创建指定时间、解析和格式化字符串、进行加减运算、比较时间、获取年月日时分秒、计算时间间隔、时区转换以及判断闰年等功能。示例代码展示了如何使用这些功能,极大简化了开发中的时间处理任务。
|
2月前
|
Java
Java快速入门之类、对象、方法
本文简要介绍了Java快速入门中的类、对象和方法。首先,解释了类和对象的概念,类是对象的抽象,对象是类的具体实例。接着,阐述了类的定义和组成,包括属性和行为,并展示了如何创建和使用对象。然后,讨论了成员变量与局部变量的区别,强调了封装的重要性,通过`private`关键字隐藏数据并提供`get/set`方法访问。最后,介绍了构造方法的定义和重载,以及标准类的制作规范,帮助初学者理解如何构建完整的Java类。
|
2月前
|
Java 程序员 调度
Java 高级面试技巧:yield() 与 sleep() 方法的使用场景和区别
本文详细解析了 Java 中 `Thread` 类的 `yield()` 和 `sleep()` 方法,解释了它们的作用、区别及为什么是静态方法。`yield()` 让当前线程释放 CPU 时间片,给其他同等优先级线程运行机会,但不保证暂停;`sleep()` 则让线程进入休眠状态,指定时间后继续执行。两者都是静态方法,因为它们影响线程调度机制而非单一线程行为。这些知识点在面试中常被提及,掌握它们有助于更好地应对多线程编程问题。
102 9