溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

java高級用法之JNA中的回調(diào)問題如何解決

發(fā)布時間:2023-05-06 09:15:48 來源:億速云 閱讀:121 作者:zzz 欄目:編程語言

這篇文章主要講解了“java高級用法之JNA中的回調(diào)問題如何解決”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“java高級用法之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的方法,那么默認(rèn)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對象來說,需要我們自行負(fù)責(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.CallbackUsage$TestLibrary$VoidCallback)
    INFO com.flydean.CallbackUsage - cbB1:Proxy interface to native function@0xffffffffc46eeefc (com.flydean.CallbackUsage$TestLibrary$ByteCallback)

    可以看出,這兩個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

    默認(rèn)情況下, 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);

    感謝各位的閱讀,以上就是“java高級用法之JNA中的回調(diào)問題如何解決”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對java高級用法之JNA中的回調(diào)問題如何解決這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!

    向AI問一下細節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI