Rumah  >  Artikel  >  Java  >  Bagaimana untuk menyelesaikan masalah panggil balik dalam JNA dalam penggunaan java lanjutan

Bagaimana untuk menyelesaikan masalah panggil balik dalam JNA dalam penggunaan java lanjutan

PHPz
PHPzke hadapan
2023-05-05 11:37:061413semak imbas

    Pengenalan

    Apakah panggilan balik? Ringkasnya, panggilan balik ialah pemberitahuan panggil balik Apabila kami perlu memberitahu tugasan tertentu selepas kaedah selesai atau peristiwa dicetuskan, kami perlu menggunakan panggilan balik.

    Bahasa yang anda paling mungkin melihat panggilan balik ialah javascript Pada asasnya dalam javascript, panggilan balik ada di mana-mana. Untuk menyelesaikan masalah neraka panggilan balik yang disebabkan oleh panggilan balik, janji telah diperkenalkan khas dalam ES6 untuk menyelesaikan masalah ini.

    Untuk memudahkan interaksi dengan kaedah asli, JNA juga menyediakan Panggilan Balik untuk panggilan balik. Intipati panggilan balik dalam JNA ialah penunjuk kepada fungsi asli Melalui penunjuk ini, kaedah dalam fungsi asli boleh dipanggil.

    Panggil Balik dalam JNA

    Pertama lihat definisi Panggilan Balik dalam JNA:

    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"));
    }

    Semua kaedah Panggilan Balik perlu melaksanakan antara muka Panggilan Balik ini. Antara muka Panggilan Balik adalah sangat mudah Ia mentakrifkan antara muka dan dua sifat.

    Mari lihat antara muka ini dahulu Nama antara muka ialah UncaughtExceptionHandler, dan terdapat kaedah uncaughtException di dalamnya. Antara muka ini digunakan terutamanya untuk mengendalikan pengecualian yang tidak ditangkap dalam kod panggil balik JAVA.

    Perhatikan bahawa pengecualian tidak boleh dilemparkan dalam kaedah uncaughtException Sebarang pengecualian yang dilemparkan daripada kaedah ini akan diabaikan.

    METHOD_NAME Medan ini menentukan kaedah untuk dipanggil melalui Panggilan Balik.

    Jika terdapat hanya satu kaedah awam yang ditakrifkan dalam kelas Panggilan Balik, maka kaedah panggil balik lalai ialah kaedah ini. Jika berbilang kaedah awam ditakrifkan dalam kelas Panggilan Balik, kaedah dengan METHOD_NAME = "panggilan balik" akan dipilih sebagai panggilan balik.

    Atribut terakhir ialah FORBIDDEN_NAMES. Menunjukkan bahawa nama dalam senarai ini tidak boleh digunakan sebagai kaedah panggil balik.

    Pada masa ini nampaknya terdapat tiga nama kaedah yang tidak boleh digunakan, iaitu: "hashCode", "equals", dan "toString".

    Panggil balik juga mempunyai adik beradik yang dipanggil DLLCallback Mari kita lihat definisi DLLCallback:

    public interface DLLCallback extends Callback {
        @java.lang.annotation.Native
        int DLL_FPTRS = 16;
    }

    DLLCallback digunakan terutamanya untuk mengakses Windows API.

    Untuk objek panggil balik, kita perlu bertanggungjawab untuk melepaskan objek panggil balik sendiri. Jika kod asli cuba mengakses panggilan balik kitar semula, ia boleh menyebabkan VM ranap.

    Aplikasi panggilan balik

    Definisi panggil balik

    Kerana panggilan balik dalam JNA sebenarnya memetakan penuding kepada fungsi dalam asli. Mula-mula, lihat penuding fungsi yang ditakrifkan dalam struct:

    struct _functions {
      int (*open)(const char*,int);
      int (*close)(int);
    };

    Dalam struktur ini, dua penunjuk fungsi ditakrifkan, dengan dua parameter dan satu parameter masing-masing.

    Takrifan panggil balik JNA yang sepadan adalah seperti berikut:

    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;
    }

    Kami mentakrifkan dua antara muka dalam Struktur yang diwarisi daripada Panggilan Balik, dan kaedah panggilan yang sepadan ditakrifkan dalam antara muka yang sepadan.

    Kemudian lihat kaedah panggilan khusus:

    Functions funcs = new Functions();
    lib.init(funcs);
    int fd = funcs.open.invoke("myfile", 0);
    funcs.close.invoke(fd);

    Selain itu, Panggilan Balik juga boleh digunakan sebagai nilai pulangan fungsi, seperti ditunjukkan di bawah:

    typedef void (*sig_t)(int);
    sig_t signal(int signal, sig_t sigfunc);

    Untuk penuding fungsi yang berasingan ini, kita perlu menyesuaikan Perpustakaan dan mentakrifkan Panggilan Balik yang sepadan di dalamnya, seperti yang ditunjukkan di bawah:

    public interface CLibrary extends Library {
        public interface SignalFunction extends Callback {
            void invoke(int signal);
        }
        SignalFunction signal(int signal, SignalFunction func);
    }

    Pemerolehan dan penggunaan panggilan balik

    Jika panggilan balik ditakrifkan dalam Struktur, maka ia boleh Structure secara automatik instantiated apabila ia dimulakan, dan kemudian anda hanya perlu mengakses sifat yang sepadan daripada Structure.

    Jika panggilan balik ditakrifkan dalam Perpustakaan biasa, ia adalah seperti berikut:

    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);
        }

    Dalam contoh di atas, kami menentukan dua panggilan balik dalam Perpustakaan, satu tanpa panggilan balik nilai pulangan, satu adalah panggilan balik yang mengembalikan bait.

    JNA menyediakan kelas alat mudah untuk membantu kami mendapatkan Panggilan Balik Kelas alat ini ialah CallbackReference, dan kaedah yang sepadan ialah CallbackReference.getCallback, seperti yang ditunjukkan di bawah:

    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);

    Hasil output adalah seperti berikut. :

    INFO com.flydean.CallbackUsage - cbV1:Antara muka proksi ke fungsi asli@0xffffffffc46eeefc (com.flydean.CallbackUsage$TestLibrary$VoidCallback)
    UFO com.Ca.llflybdean c. antara muka ke fungsi asli@0xffffffffc46eeefc (com.flydean.CallbackUsage$TestLibrary$ByteCallback)

    Dapat dilihat bahawa kedua-dua Panggilan Balik ini sebenarnya adalah proksi untuk kaedah asli. Jika anda melihat logik pelaksanaan getCallback secara terperinci:

    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;
            }
        }

    anda boleh melihat bahawa logik pelaksanaannya adalah untuk menentukan terlebih dahulu sama ada jenis itu adalah antara muka Jika ia bukan antara muka, ralat akan dilaporkan. Kemudian tentukan sama ada ia adalah pemetaan langsung. Malah, pelaksanaan semasa JNA adalah semua pemetaan antara muka, jadi logik seterusnya adalah untuk mendapatkan panggilan balik yang sepadan dengan penunjuk fungsi daripada pointerCallbackMap. Kemudian cari Panggilan Balik khusus mengikut jenis yang diluluskan.

    Jika ia tidak ditemui, buat panggilan balik baharu dan akhirnya simpan panggilan balik yang baru dibuat dalam pointerCallbackMap.

    Semua orang harus ambil perhatian bahawa terdapat parameter utama di sini yang dipanggil Penunjuk Apabila benar-benar menggunakannya, anda perlu menghantar penunjuk ke fungsi naitve sebenar. Dalam contoh di atas, demi kesederhanaan, kami menyesuaikan Penunjuk, yang tidak mempunyai banyak kepentingan praktikal.

    如果真的要想在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方法是在当前的线程中执行的。如果希望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);

    Atas ialah kandungan terperinci Bagaimana untuk menyelesaikan masalah panggil balik dalam JNA dalam penggunaan java lanjutan. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

    Kenyataan:
    Artikel ini dikembalikan pada:yisu.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam