詳解JNA中的回調(diào)方法
簡(jiǎn)介
什么是 callback 呢?簡(jiǎn)單點(diǎn)說 callback 就是回調(diào)通知,當(dāng)我們需要在某個(gè)方法完成之后,或者某個(gè)事件觸發(fā)之后,來通知進(jìn)行某些特定的任務(wù)就需要用到 callback 了。
最有可能看到 callback 的語(yǔ)言就是 javascript 了,基本上在 javascript 中,callback 無處不在。為了解決 callback 導(dǎo)致的回調(diào)地獄的問題,ES6 中特意引入了 promise 來解決這個(gè)問題。
為了方便和 native 方法進(jìn)行交互,JNA 中同樣提供了 Callback 用來進(jìn)行回調(diào)。JNA 中回調(diào)的本質(zhì)是一個(gè)指向 native 函數(shù)的指針,通過這個(gè)指針可以調(diào)用 native 函數(shù)中的方法,一起來看看吧。
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 方法都需要實(shí)現(xiàn)這個(gè) Callback 接口。Callback 接口很簡(jiǎn)單,里面定義了一個(gè) interface 和兩個(gè)屬性。
先來看這個(gè) interface,interface 名字叫做 UncaughtExceptionHandler, 里面有一個(gè) uncaughtException 方法。這個(gè) interface 主要用于處理 JAVA 的 callback 代碼中沒有捕獲的異常。
注意,在 uncaughtException 方法中,不能拋出異常,任何從這個(gè)方法拋出的異常都會(huì)被忽略。
METHOD_NAME 這個(gè)字段指定了 Callback 要調(diào)用的方法。
如果 Callback 類中只定義了一個(gè) public 的方法,那么默認(rèn) callback 方法就是這個(gè)方法。如果 Callback 類中定義了多個(gè) public 方法,那么會(huì)選擇 METHOD_NAME = “callback” 的這個(gè)方法作為 callback。
最后一個(gè)屬性就是 FORBIDDEN_NAMES。表示在這個(gè)列表里面的名字是不能作為 callback 方法使用的。
目前看來是有三個(gè)方法名不能夠被使用,分別是:”hashCode”, “equals”, “toString”。
Callback 還有一個(gè)同胞兄弟叫做 DLLCallback,我們來看下 DLLCallback 的定義:
public interface DLLCallback extends Callback {
@java.lang.annotation.Native
int DLL_FPTRS = 16;
}DLLCallback 主要是用在 Windows API 的訪問中。
對(duì)于 callback 對(duì)象來說,需要我們自行負(fù)責(zé)對(duì) callback 對(duì)象的釋放工作。如果 native 代碼嘗試訪問一個(gè)被回收的 callback,那么有可能會(huì)導(dǎo)致 VM 崩潰。
callback 的應(yīng)用
callback 的定義
因?yàn)?JNA 中的 callback 實(shí)際上映射的是 native 中指向函數(shù)的指針。首先看一下在 struct 中定義的函數(shù)指針:
struct _functions {
int (*open)(const char*,int);
int (*close)(int);
};在這個(gè)結(jié)構(gòu)體中,定義了兩個(gè)函數(shù)指針,分別帶兩個(gè)參數(shù)和一個(gè)參數(shù)。
對(duì)應(yīng)的 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;
}我們?cè)?Structure 里面定義兩個(gè)接口繼承自 Callback,對(duì)應(yīng)的接口中定義了相應(yīng)的 invoke 方法。
然后看一下具體的調(diào)用方式:
Functions funcs = new Functions();
lib.init(funcs);
int fd = funcs.open.invoke("myfile", 0);
funcs.close.invoke(fd);另外 Callback 還可以作為函數(shù)的返回值,如下所示:
typedef void (*sig_t)(int); sig_t signal(int signal, sig_t sigfunc);
對(duì)于這種單獨(dú)存在的函數(shù)指針,我們需要自定義一個(gè) Library, 并在其中定義對(duì)應(yīng)的 Callback,如下所示:
public interface CLibrary extends Library {
public interface SignalFunction extends Callback {
void invoke(int signal);
}
SignalFunction signal(int signal, SignalFunction func);
}callback 的獲取和應(yīng)用
如果 callback 是定義在 Structure 中的,那么可以在 Structure 進(jìn)行初始化的時(shí)候自動(dòng)實(shí)例化,然后只需要從 Structure 中訪問對(duì)應(yīng)的屬性即可。
如果 callback 定義是在一個(gè)普通的 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);
}上例中,我們?cè)谝粋€(gè) Library 中定義了兩個(gè) callback,一個(gè)是無返回值的 callback,一個(gè)是返回 byte 的 callback。
JNA 提供了一個(gè)簡(jiǎn)單的工具類來幫助我們獲取 Callback,這個(gè)工具類就是 CallbackReference,對(duì)應(yīng)的方法是 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);輸出結(jié)果如下:
INFO com.flydean.CallbackUsage - cbV1:Proxy interface to native function@0xffffffffc46eeefc (com.flydean.CallbackUsageTestLibraryVoidCallback)
INFO com.flydean.CallbackUsage - cbB1:Proxy interface to native function@0xffffffffc46eeefc (com.flydean.CallbackUsageTestLibraryByteCallback)
可以看出,這兩個(gè) Callback 實(shí)際上是對(duì) native 方法的代理。如果詳細(xì)看 getCallback 的實(shí)現(xiàn)邏輯:
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;
}
}可以看到它的實(shí)現(xiàn)邏輯是首先判斷 type 是否是 interface,如果不是 interface 則會(huì)報(bào)錯(cuò)。然后判斷是否是 direct mapping。實(shí)際上當(dāng)前 JNA 的實(shí)現(xiàn)都是 interface mapping,所以接下來的邏輯就是從 pointerCallbackMap 中獲取函數(shù)指針對(duì)應(yīng)的 callback。然后按照傳入的類型來查找具體的 Callback。
如果沒有查找到,則創(chuàng)建一個(gè)新的 callback,最后將這個(gè)新創(chuàng)建的存入 pointerCallbackMap 中。
大家要注意, 這里有一個(gè)關(guān)鍵的參數(shù)叫做 Pointer,實(shí)際使用的時(shí)候,需要傳入指向真實(shí) naitve 函數(shù)的指針。上面的例子中,為了簡(jiǎn)便起見,我們是自定義了一個(gè) Pointer,這個(gè) Pointer 并沒有太大的實(shí)際意義。
如果真的要想在 JNA 中調(diào)用在 TestLibrary 中創(chuàng)建的兩個(gè) call 方法:callVoidCallback 和 callInt8Callback,首先需要加載對(duì)應(yīng)的 Library:
TestLibrary lib = Native.load("testlib", TestLibrary.class);
然后分別創(chuàng)建 TestLibrary.VoidCallback 和 TestLibrary.ByteCallback 的實(shí)例如下,首先看一下 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]);這里我們?cè)?callback 中將 voidCalled 的值回寫為 true 表示已經(jīng)調(diào)用了 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 值即可。
在多線程環(huán)境中使用 callback
默認(rèn)情況下, callback 方法是在當(dāng)前的線程中執(zhí)行的。如果希望 callback 方法是在另外的線程中執(zhí)行,則可以創(chuàng)建一個(gè) 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);然后創(chuàng)建 callback 的實(shí)例:
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);
}
}
};然后調(diào)用:
Native.setCallbackThreadInitializer(cb, init);
將 callback 和 CallbackThreadInitializer 進(jìn)行關(guān)聯(lián)。
最后調(diào)用 callback 方法即可:
lib.callVoidCallbackThreaded(cb, 2, 2000, "callVoidCallbackThreaded", 0);
總結(jié)
JNA 中的 callback 可以實(shí)現(xiàn)向 native 方法中傳遞方法的作用,在某些情況下用處還是非常大的。
本文的代碼:https://github.com/ddean2009/learn-java-base-9-to-20.git
到此這篇關(guān)于JNA中的回調(diào)方法的文章就介紹到這了,更多相關(guān)jna回調(diào)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring+Quartz實(shí)現(xiàn)動(dòng)態(tài)任務(wù)調(diào)度詳解
這篇文章主要介紹了Spring+Quartz實(shí)現(xiàn)動(dòng)態(tài)任務(wù)調(diào)度詳解,最近經(jīng)常基于spring?boot寫定時(shí)任務(wù),并且是使用注解的方式進(jìn)行實(shí)現(xiàn),分成的方便將自己的類注入spring容器,需要的朋友可以參考下2024-01-01
Stream distinct根據(jù)list某個(gè)字段去重的解決方案
這篇文章主要介紹了Stream distinct根據(jù)list某個(gè)字段去重,stream的distinct去重方法,是根據(jù) Object.equals,和 Object.hashCode這兩個(gè)方法來判斷是否重復(fù)的,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-05-05
帶你輕松搞定Java面向?qū)ο蟮木幊?-數(shù)組,集合框架
Java是面向?qū)ο蟮母呒?jí)編程語(yǔ)言,類和對(duì)象是 Java程序的構(gòu)成核心。圍繞著Java類和Java對(duì)象,有三大基本特性:封裝是Java 類的編寫規(guī)范、繼承是類與類之間聯(lián)系的一種形式、而多態(tài)為系統(tǒng)組件或模塊之間解耦提供了解決方案2021-06-06
Javafx簡(jiǎn)單實(shí)現(xiàn)【我的電腦資源管理器】效果
這篇文章主要介紹了Javafx簡(jiǎn)單實(shí)現(xiàn)【我的電腦資源管理器】效果,涉及Javafx操作系統(tǒng)文件模擬資源管理器的實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-09-09
Mybatis_plus基礎(chǔ)教程(總結(jié)篇)
這篇文章主要介紹了Mybatis_plus基礎(chǔ)教程(總結(jié)篇),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09

