java高级用法之:JNA中的回调
简介
什么是callback呢?简单点说callback就是回调通知,当我们需要在某个方法完成之后,或者某个事件触发之后,来通知进行某些特定的任务就需要用到callback了。
最有可能看到callback的语言就是javascript了,基本上在javascript中,callback无处不在。为了解决callback导致的回调地狱的问题,ES6中特意引入了promise来解决这个问题。
为了方便和native方法进行交互,JNA中同样提供了Callback用来进行回调。JNA中回调的本质是一个指向native函数的指针,通过这个指针可以调用native函数中的方法,一起来看看吧。
JNA中的Callback
先看下JNA中Callback的定义:
public interface Callback {
interface UncaughtExceptionHandler {
void uncaughtException(Callback c, Throwable e);
}
String METHOD_NAME = "callback";
List<String> FORBIDDEN_NAMES = Collections.unmodifiableList(
Arrays.asList("hashCode", "equals", "toString"));
}
所有的Callback方法都需要实现这个Callback接口。Callback接口很简单,里面定义了一个interface和两个属性。
先来看这个interface,interface名字叫做UncaughtExceptionHandler,里面有一个uncaughtException方法。这个interface主要用于处理JAVA的callback代码中没有捕获的异常。
注意,在uncaughtException方法中,不能抛出异常,任何从这个方法抛出的异常都会被忽略。
METHOD_NAME这个字段指定了Callback要调用的方法。
如果Callback类中只定义了一个public的方法,那么默认callback方法就是这个方法。如果Callback类中定义了多个public方法,那么会选择METHOD_NAME = "callback"的这个方法作为callback。
最后一个属性就是FORBIDDEN_NAMES。表示在这个列表里面的名字是不能作为callback方法使用的。
目前看来是有三个方法名不能够被使用,分别是:"hashCode", "equals", "toString"。
Callback还有一个同胞兄弟叫做DLLCallback,我们来看下DLLCallback的定义:
public interface DLLCallback extends Callback {
@java.lang.annotation.Native
int DLL_FPTRS = 16;
}
DLLCallback主要是用在Windows API的访问中。
对于callback对象来说,需要我们自行负责对callback对象的释放工作。如果native代码尝试访问一个被回收的callback,那么有可能会导致VM崩溃。
callback的应用
callback的定义
因为JNA中的callback实际上映射的是native中指向函数的指针。首先看一下在struct中定义的函数指针:
struct _functions {
int (*open)(const char*,int);
int (*close)(int);
};
在这个结构体中,定义了两个函数指针,分别带两个参数和一个参数。
对应的JNA的callback定义如下:
public class Functions extends Structure {
public static interface OpenFunc extends Callback {
int invoke(String name, int options);
}
public static interface CloseFunc extends Callback {
int invoke(int fd);
}
public OpenFunc open;
public CloseFunc close;
}
我们在Structure里面定义两个接口继承自Callback,对应的接口中定义了相应的invoke方法。
然后看一下具体的调用方式:
Functions funcs = new Functions();
lib.init(funcs);
int fd = funcs.open.invoke("myfile", 0);
funcs.close.invoke(fd);
另外Callback还可以作为函数的返回值,如下所示:
typedef void (*sig_t)(int);
sig_t signal(int signal, sig_t sigfunc);
对于这种单独存在的函数指针,我们需要自定义一个Library,并在其中定义对应的Callback,如下所示:
public interface CLibrary extends Library {
public interface SignalFunction extends Callback {
void invoke(int signal);
}
SignalFunction signal(int signal, SignalFunction func);
}
callback的获取和应用
如果callback是定义在Structure中的,那么可以在Structure进行初始化的时候自动实例化,然后只需要从Structure中访问对应的属性即可。
如果callback定义是在一个普通的Library中的话,如下所示:
public static interface TestLibrary extends Library {
interface VoidCallback extends Callback {
void callback();
}
interface ByteCallback extends Callback {
byte callback(byte arg, byte arg2);
}
void callVoidCallback(VoidCallback c);
byte callInt8Callback(ByteCallback c, byte arg, byte arg2);
}
上例中,我们在一个Library中定义了两个callback,一个是无返回值的callback,一个是返回byte的callback。
JNA提供了一个简单的工具类来帮助我们获取Callback,这个工具类就是CallbackReference,对应的方法是CallbackReference.getCallback,如下所示:
Pointer p = new Pointer("MultiplyMappedCallback".hashCode());
Callback cbV1 = CallbackReference.getCallback(TestLibrary.VoidCallback.class, p);
Callback cbB1 = CallbackReference.getCallback(TestLibrary.ByteCallback.class, p);
log.info("cbV1:{}",cbV1);
log.info("cbB1:{}",cbB1);
输出结果如下:
INFO com.flydean.CallbackUsage - cbV1:Proxy interface to native function@0xffffffffc46eeefc (com.flydean.CallbackUsage$TestLibrary$VoidCallback)
INFO com.flydean.CallbackUsage - cbB1:Proxy interface to native function@0xffffffffc46eeefc (com.flydean.CallbackUsage$TestLibrary$ByteCallback)
可以看出,这两个Callback实际上是对native方法的代理。如果详细看getCallback的实现逻辑:
private static Callback getCallback(Class<?> type, Pointer p, boolean direct) {
if (p == null) {
return null;
}
if (!type.isInterface())
throw new IllegalArgumentException("Callback type must be an interface");
Map<Callback, CallbackReference> map = direct ? directCallbackMap : callbackMap;
synchronized(pointerCallbackMap) {
Reference<Callback>[] array = pointerCallbackMap.get(p);
Callback cb = getTypeAssignableCallback(type, array);
if (cb != null) {
return cb;
}
cb = createCallback(type, p);
pointerCallbackMap.put(p, addCallbackToArray(cb,array));
// No CallbackReference for this callback
map.remove(cb);
return cb;
}
}
可以看到它的实现逻辑是首先判断type是否是interface,如果不是interface则会报错。然后判断是否是direct mapping。实际上当前JNA的实现都是interface mapping,所以接下来的逻辑就是从pointerCallbackMap中获取函数指针对应的callback。然后按照传入的类型来查找具体的Callback。
如果没有查找到,则创建一个新的callback,最后将这个新创建的存入pointerCallbackMap中。
大家要注意, 这里有一个关键的参数叫做Pointer,实际使用的时候,需要传入指向真实naitve函数的指针。上面的例子中,为了简便起见,我们是自定义了一个Pointer,这个Pointer并没有太大的实际意义。
如果真的要想在JNA中调用在TestLibrary中创建的两个call方法:callVoidCallback和callInt8Callback,首先需要加载对应的Library:
TestLibrary lib = Native.load("testlib", TestLibrary.class);
然后分别创建TestLibrary.VoidCallback和TestLibrary.ByteCallback的实例如下,首先看一下VoidCallback:
final boolean[] voidCalled = { false };
TestLibrary.VoidCallback cb1 = new TestLibrary.VoidCallback() {
@Override
public void callback() {
voidCalled[0] = true;
}
};
lib.callVoidCallback(cb1);
assertTrue("Callback not called", voidCalled[0]);
这里我们在callback中将voidCalled的值回写为true表示已经调用了callback方法。
再看看带返回值的ByteCallback:
final boolean[] int8Called = {false};
final byte[] cbArgs = { 0, 0 };
TestLibrary.ByteCallback cb2 = new TestLibrary.ByteCallback() {
@Override
public byte callback(byte arg, byte arg2) {
int8Called[0] = true;
cbArgs[0] = arg;
cbArgs[1] = arg2;
return (byte)(arg + arg2);
}
};
final byte MAGIC = 0x11;
byte value = lib.callInt8Callback(cb2, MAGIC, (byte)(MAGIC*2));
我们直接在callback方法中返回要返回的byte值即可。
在多线程环境中使用callback
默认情况下, callback方法是在当前的线程中执行的。如果希望callback方法是在另外的线程中执行,则可以创建一个CallbackThreadInitializer,指定daemon,detach,name,和threadGroup属性:
final String tname = "VoidCallbackThreaded";
ThreadGroup testGroup = new ThreadGroup("Thread group for callVoidCallbackThreaded");
CallbackThreadInitializer init = new CallbackThreadInitializer(true, false, tname, testGroup);
然后创建callback的实例:
TestLibrary.VoidCallback cb = new TestLibrary.VoidCallback() {
@Override
public void callback() {
Thread thread = Thread.currentThread();
daemon[0] = thread.isDaemon();
name[0] = thread.getName();
group[0] = thread.getThreadGroup();
t[0] = thread;
if (thread.isAlive()) {
alive[0] = true;
}
++called[0];
if (THREAD_DETACH_BUG && called[0] == 2) {
Native.detach(true);
}
}
};
然后调用:
Native.setCallbackThreadInitializer(cb, init);
将callback和CallbackThreadInitializer进行关联。
最后调用callback方法即可:
lib.callVoidCallbackThreaded(cb, 2, 2000, "callVoidCallbackThreaded", 0);
总结
JNA中的callback可以实现向native方法中传递方法的作用,在某些情况下用处还是非常大的。
本文的代码:https://github.com/ddean2009/learn-java-base-9-to-20.git
本文已收录于 http://www.flydean.com/09-jna-callbacks/
最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!
java高级用法之:JNA中的回调的更多相关文章
- java高级用法之:JNA中的Structure
目录 简介 native中的struct Structure 特殊类型的Structure 结构体数组作为参数 结构体数组作为返回值 结构体中的结构体 结构体中的数组 结构体中的可变字段 结构体中的只 ...
- java高级用法之:JNA中的Memory和Pointer
目录 简介 Pointer 特殊的Pointer:Opaque Memory 总结 简介 我们知道在native的代码中有很多指针,这些指针在JNA中被映射成为Pointer.除了Pointer之外, ...
- java高级用法之:JNA中的Function
目录 简介 function的定义 Function的实际应用 总结 简介 在JNA中,为了和native的function进行映射,我们可以有两种mapping方式,第一种是interface ma ...
- java高级用法之:JNA类型映射应该注意的问题
目录 简介 String Buffers,Memory,数组和Pointer 可变参数 总结 简介 JNA提供JAVA类型和native类型的映射关系,但是这一种映射关系只是一个大概的映射,我们在实际 ...
- java高级用法之:在JNA中将本地方法映射到JAVA代码中
目录 简介 Library Mapping Function Mapping Invocation Mapping 防止VM崩溃 性能考虑 总结 简介 不管是JNI还是JNA,最终调用的都是nativ ...
- java高级用法之:在JNA中使用类型映射
目录 简介 类型映射的本质 TypeMapper NativeMapped 总结 简介 JNA中有很多种映射,library的映射,函数的映射还有函数参数和返回值的映射,libary和函数的映射比较简 ...
- java高级用法之:调用本地方法的利器JNA
目录 简介 JNA初探 JNA加载native lib的流程 本地方法中的结构体参数 总结 简介 JAVA是可以调用本地方法的,官方提供的调用方式叫做JNI,全称叫做java native inter ...
- java 高级用法整理
一.retentionpolicy.class vs runtime区别 java5,增加了注解的功能:其中retentionpolicy注解的生命周期,提供了三种选择策略 source.class和 ...
- java高级用法之:无所不能的java,本地方法调用实况
目录 简介 JDK的本地方法 自定义native方法 总结 简介 相信每个程序员都有一个成为C++大师的梦想,毕竟C++程序员处于程序员鄙视链的顶端,他可以俯视任何其他语言的程序员. 但事实情况是,无 ...
随机推荐
- 常见算法的时间复杂度(大O计数法)
定义 对于不同的机器环境而言,确切的单位时间是不同的,但是对于算法进行多少个基本操作(即花费多少时间单位)在规模数量级上却是相同的,由此可以忽略机器环境的影响而客观的反应算法的时间效率. 对于算法 ...
- 动态规划 洛谷P1616 疯狂的采药
动态规划 洛谷P1616 疯狂的采药 同样也是洛谷的动态规划一个普及-的题目,接下来分享一下我做题代码 看到题目,没很认真的看数据大小,我就提交了我的代码: 1 //动态规划 洛谷P1616 疯狂的采 ...
- Windows常用快捷操作
Windows操作系统作为目前最广泛使用的PC端OS,掌握一些快捷键,方便快速在Windows系统下进行操作. 下面收集整理了一些常用的快捷操作: Ctrl + A 全选 Ctrl + C 复 ...
- vulnhub靶机djinn:1渗透笔记
djinn:1渗透笔记 靶机下载地址:https://www.vulnhub.com/entry/djinn-1,397/ 信息收集 首先我们嘚确保一点,kali机和靶机处于同一网段,查看kali i ...
- 利用Matlab快速绘制栅格地图
代码演示 % 基于栅格地图的机器人路径规划算法 % 第1节:利用Matlab快速绘制栅格地图 clc clear close all %% 构建颜色MAP图 cmap = [1 1 1; ... % ...
- 使用Visual Studio查看C++类内存分布
书上类继承相关章节到这里就结束了,这里不妨说下C++内存分布结构,我们来看看编译器是怎么处理类成员内存分布的,特别是在继承.虚函数存在的情况下. 工欲善其事,必先利其器,我们先用好Visual Stu ...
- [翻译]Service workers:PWA背后的英雄
原文地址:https://medium.freecodecamp.org/service-workers-the-little-heroes-behind-progressive-web-apps-4 ...
- IDEA中 Debug 调试工具(图文详解)
DEBUG调试工具 一. Debug 调试工具 1. Debug的作用 2. Debug的使用步骤 3. IDEA中Debug按钮详解 总结 参考博文:https://blog.csdn.net/qq ...
- 【.NET 6】使用EF Core 访问Oracle+Mysql+PostgreSQL并进行简单增改操作与性能比较
前言 唠嗑一下.都在说去O或者开源,但是对于数据库选型来说,很多人却存在着误区.例如,去O,狭义上讲,是去Oracle数据库.但是从广义上来说,是去Oracle公司产品或者具有漂亮国垄断地位和需要商 ...
- Dockerfile入门
1.Dockerfile介绍 在之前Docker的使用中,我们直接从仓库下载需要的镜像到本地,然后稍加配置就可以应用了,通常从仓库下载下来的镜像都是通用的,无任何私有化的东西,我们拿过来就需要加很多的 ...