함께 쓰는 습관을 들이세요! "너겟 데일리 뉴플랜 · 4월 업데이트 챌린지" 참여 12일차 입니다. 클릭하시면 이벤트 내용을 보실 수 있습니다 .
소개
콜백이란 무엇입니까? 콜백은 간단히 말해서 콜백 알림인데 메소드가 완료되거나 이벤트가 발생한 후 특정 작업을 알려야 할 때 콜백을 사용해야 합니다.
콜백을 가장 많이 볼 수 있는 언어는 자바스크립트인데 기본적으로 자바스크립트에서 콜백은 어디에나 있습니다. 콜백으로 인한 콜백 지옥 문제를 해결하기 위해 ES6에서는 이 문제를 해결하겠다는 약속을 특별히 도입했습니다.
네이티브 메소드와의 상호 작용을 용이하게 하기 위해 JNA는 콜백을 위한 콜백도 제공합니다. JNA에서 콜백의 본질은 네이티브 함수에 대한 포인터이며, 이를 통해 네이티브 함수의 메서드를 호출할 수 있습니다. 살펴보겠습니다.
JNA의 콜백
먼저 JNA에서 콜백의 정의를 살펴보십시오.
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"));
}
复制代码
모든 콜백 메소드는 이 콜백 인터페이스를 구현해야 합니다. 콜백 인터페이스는 매우 간단하며 인터페이스와 두 가지 속성을 정의합니다.
먼저 이 인터페이스를 살펴보겠습니다. 인터페이스 이름은 uncaughtException 메서드가 있는 UncaughtExceptionHandler입니다. 이 인터페이스는 주로 JAVA의 콜백 코드에서 catch되지 않은 예외를 처리하는 데 사용됩니다.
uncaughtException 메서드에서는 예외가 throw될 수 없으며 이 메서드에서 throw된 모든 예외는 무시됩니다.
METHOD_NAME 필드는 콜백에서 호출할 메소드를 지정합니다.
Callback 클래스에 정의된 공용 메서드가 하나만 있는 경우 기본 콜백 메서드는 이 메서드입니다. Callback 클래스에 여러 공용 메서드가 정의되어 있는 경우 METHOD_NAME = "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
기본적으로 콜백 메서드는 현재 스레드에서 실행됩니다. 다른 스레드에서 콜백 메서드를 실행하려면 CallbackThreadInitializer를 만들고 데몬, 분리, 이름 및 threadGroup 속성을 지정할 수 있습니다.
final String tname = "VoidCallbackThreaded";
ThreadGroup testGroup = new ThreadGroup("Thread group for callVoidCallbackThreaded");
CallbackThreadInitializer init = new CallbackThreadInitializer(true, false, tname, testGroup);
复制代码
그런 다음 콜백 인스턴스를 만듭니다.
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);
复制代码
CallbackThreadInitializer와 콜백을 연결합니다.
마지막으로 콜백 메서드를 호출합니다.
lib.callVoidCallbackThreaded(cb, 2, 2000, "callVoidCallbackThreaded", 0);
复制代码
요약하다
JNA의 콜백은 메소드를 네이티브 메소드로 전달하는 기능을 실현할 수 있으며, 이는 경우에 따라 매우 유용합니다.
이 기사의 코드: github.com/ddean2009/l…
이 기사는 www.flydean.com/09-jna-call 에 포함되었습니다 …
가장 인기 있는 해석, 가장 심오한 건조 제품, 가장 간결한 자습서 및 모르는 많은 트릭이 발견하기를 기다리고 있습니다!
내 공식 계정에 관심을 가져 주셔서 감사합니다. "프로그래밍", 기술 이해, 더 나은 이해!