java高級(jí)用法之JNA中的回調(diào)問(wèn)題
簡(jiǎn)介
什么是callback呢?簡(jiǎn)單點(diǎn)說(shuō)callback就是回調(diào)通知,當(dāng)我們需要在某個(gè)方法完成之后,或者某個(gè)事件觸發(fā)之后,來(lái)通知進(jìn)行某些特定的任務(wù)就需要用到callback了。
最有可能看到callback的語(yǔ)言就是javascript了,基本上在javascript中,callback無(wú)處不在。為了解決callback導(dǎo)致的回調(diào)地獄的問(wèn)題,ES6中特意引入了promise來(lái)解決這個(gè)問(wèn)題。
為了方便和native方法進(jìn)行交互,JNA中同樣提供了Callback用來(lái)進(jìn)行回調(diào)。JNA中回調(diào)的本質(zhì)是一個(gè)指向native函數(shù)的指針,通過(guò)這個(gè)指針可以調(diào)用native函數(shù)中的方法,一起來(lái)看看吧。
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è)屬性。
先來(lái)看這個(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方法使用的。
目前看來(lái)是有三個(gè)方法名不能夠被使用,分別是:“hashCode”, “equals”, “toString”。
Callback還有一個(gè)同胞兄弟叫做DLLCallback,我們來(lái)看下DLLCallback的定義:
public interface DLLCallback extends Callback { @java.lang.annotation.Native int DLL_FPTRS = 16; }
DLLCallback主要是用在Windows API的訪問(wèn)中。
對(duì)于callback對(duì)象來(lái)說(shuō),需要我們自行負(fù)責(zé)對(duì)callback對(duì)象的釋放工作。如果native代碼嘗試訪問(wèn)一個(gè)被回收的callback,那么有可能會(huì)導(dǎo)致VM崩潰。
callback的應(yīng)用
callback的定義
因?yàn)镴NA中的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è)赟tructure里面定義兩個(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中訪問(wèn)對(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è)是無(wú)返回值的callback,一個(gè)是返回byte的callback。
JNA提供了一個(gè)簡(jiǎn)單的工具類來(lái)幫助我們獲取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.CallbackUsage$TestLibrary$VoidCallback)
INFO com.flydean.CallbackUsage - cbB1:Proxy interface to native function@0xffffffffc46eeefc (com.flydean.CallbackUsage$TestLibrary$ByteCallback)
可以看出,這兩個(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,所以接下來(lái)的邏輯就是從pointerCallbackMap中獲取函數(shù)指針對(duì)應(yīng)的callback。然后按照傳入的類型來(lái)查找具體的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è)赾allback中將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)于java高級(jí)用法之:JNA中的回調(diào)的文章就介紹到這了,更多相關(guān)java JNA回調(diào)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于springboot和redis實(shí)現(xiàn)單點(diǎn)登錄
這篇文章主要為大家詳細(xì)介紹了基于springboot和redis實(shí)現(xiàn)單點(diǎn)登錄,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-06-06java實(shí)現(xiàn)搶紅包算法(公平版和手速版)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)搶紅包算法,分為公平版和手速版,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-09-09使用Java獲取系統(tǒng)信息的常用代碼整理總結(jié)
這篇文章主要介紹了使用Java獲取系統(tǒng)信息的常用代碼整理總結(jié),在服務(wù)器端一般經(jīng)常能夠用到,歡迎收藏,需要的朋友可以參考下2015-11-11SpringBoot整合第三方技術(shù)的實(shí)現(xiàn)
本文主要介紹了SpringBoot整合第三方技術(shù)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02Spring?Security如何實(shí)現(xiàn)升級(jí)密碼加密方式詳解
這篇文章主要為大家介紹了Spring?Security實(shí)現(xiàn)升級(jí)密碼加密方式詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01