您好,登錄后才能下訂單哦!
今天小編給大家分享一下java高級用法之JNA中的回調問題怎么解決的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
什么是callback呢?簡單點說callback就是回調通知,當我們需要在某個方法完成之后,或者某個事件觸發之后,來通知進行某些特定的任務就需要用到callback了。
最有可能看到callback的語言就是javascript了,基本上在javascript中,callback無處不在。為了解決callback導致的回調地獄的問題,ES6中特意引入了promise來解決這個問題。
為了方便和native方法進行交互,JNA中同樣提供了Callback用來進行回調。JNA中回調的本質是一個指向native函數的指針,通過這個指針可以調用native函數中的方法,一起來看看吧。
先看下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崩潰。
因為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是定義在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方法是在另外的線程中執行,則可以創建一個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);
以上就是“java高級用法之JNA中的回調問題怎么解決”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。