詳解JNA中的回調(diào)方法
簡介
什么是 callback 呢?簡單點說 callback 就是回調(diào)通知,當(dāng)我們需要在某個方法完成之后,或者某個事件觸發(fā)之后,來通知進行某些特定的任務(wù)就需要用到 callback 了。
最有可能看到 callback 的語言就是 javascript 了,基本上在 javascript 中,callback 無處不在。為了解決 callback 導(dǎo)致的回調(diào)地獄的問題,ES6 中特意引入了 promise 來解決這個問題。
為了方便和 native 方法進行交互,JNA 中同樣提供了 Callback 用來進行回調(diào)。JNA 中回調(diào)的本質(zhì)是一個指向 native 函數(shù)的指針,通過這個指針可以調(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 方法都需要實現(xiàn)這個 Callback 接口。Callback 接口很簡單,里面定義了一個 interface 和兩個屬性。
先來看這個 interface,interface 名字叫做 UncaughtExceptionHandler, 里面有一個 uncaughtException 方法。這個 interface 主要用于處理 JAVA 的 callback 代碼中沒有捕獲的異常。
注意,在 uncaughtException 方法中,不能拋出異常,任何從這個方法拋出的異常都會被忽略。
METHOD_NAME 這個字段指定了 Callback 要調(diào)用的方法。
如果 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 對象來說,需要我們自行負責(zé)對 callback 對象的釋放工作。如果 native 代碼嘗試訪問一個被回收的 callback,那么有可能會導(dǎo)致 VM 崩潰。
callback 的應(yīng)用
callback 的定義
因為 JNA 中的 callback 實際上映射的是 native 中指向函數(shù)的指針。首先看一下在 struct 中定義的函數(shù)指針:
struct _functions { int (*open)(const char*,int); int (*close)(int); };
在這個結(jié)構(gòu)體中,定義了兩個函數(shù)指針,分別帶兩個參數(shù)和一個參數(shù)。
對應(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; }
我們在 Structure 里面定義兩個接口繼承自 Callback,對應(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);
對于這種單獨存在的函數(shù)指針,我們需要自定義一個 Library, 并在其中定義對應(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 進行初始化的時候自動實例化,然后只需要從 Structure 中訪問對應(yīng)的屬性即可。
如果 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,對應(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)
可以看出,這兩個 Callback 實際上是對 native 方法的代理。如果詳細看 getCallback 的實現(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; } }
可以看到它的實現(xiàn)邏輯是首先判斷 type 是否是 interface,如果不是 interface 則會報錯。然后判斷是否是 direct mapping。實際上當(dāng)前 JNA 的實現(xiàn)都是 interface mapping,所以接下來的邏輯就是從 pointerCallbackMap 中獲取函數(shù)指針對應(yīng)的 callback。然后按照傳入的類型來查找具體的 Callback。
如果沒有查找到,則創(chuàng)建一個新的 callback,最后將這個新創(chuàng)建的存入 pointerCallbackMap 中。
大家要注意, 這里有一個關(guān)鍵的參數(shù)叫做 Pointer,實際使用的時候,需要傳入指向真實 naitve 函數(shù)的指針。上面的例子中,為了簡便起見,我們是自定義了一個 Pointer,這個 Pointer 并沒有太大的實際意義。
如果真的要想在 JNA 中調(diào)用在 TestLibrary 中創(chuàng)建的兩個 call 方法:callVoidCallback 和 callInt8Callback,首先需要加載對應(yīng)的 Library:
TestLibrary lib = Native.load("testlib", TestLibrary.class);
然后分別創(chuàng)建 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 表示已經(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
默認情況下, callback 方法是在當(dāng)前的線程中執(zhí)行的。如果希望 callback 方法是在另外的線程中執(zhí)行,則可以創(chuàng)建一個 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 的實例:
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 進行關(guān)聯(lián)。
最后調(diào)用 callback 方法即可:
lib.callVoidCallbackThreaded(cb, 2, 2000, "callVoidCallbackThreaded", 0);
總結(jié)
JNA 中的 callback 可以實現(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)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
如何使用IDEA查看java文件編譯后的字節(jié)碼內(nèi)容
這篇文章主要介紹了如何使用IDEA查看java文件編譯后的字節(jié)碼內(nèi)容,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03Druid(新版starter)在SpringBoot下的使用教程
Druid是Java語言中最好的數(shù)據(jù)庫連接池,Druid能夠提供強大的監(jiān)控和擴展功能,DruidDataSource支持的數(shù)據(jù)庫,這篇文章主要介紹了Druid(新版starter)在SpringBoot下的使用,需要的朋友可以參考下2023-05-05java中應(yīng)用Stack進行算術(shù)運算的操作
這篇文章主要介紹了java中應(yīng)用Stack進行算術(shù)運算的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-03-03Java 動態(tài)數(shù)組的實現(xiàn)示例
Java動態(tài)數(shù)組是一種可以任意伸縮數(shù)組長度的對象,本文主要介紹了Java 動態(tài)數(shù)組的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-08-08