简介

什么是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/

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

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