关于java:java高级用法之JNA中的回调

10次阅读

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

简介

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

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

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

正文完
 0