Maison  >  Article  >  Java  >  Comment résoudre le problème de rappel dans JNA lors d'une utilisation avancée de Java

Comment résoudre le problème de rappel dans JNA lors d'une utilisation avancée de Java

PHPz
PHPzavant
2023-05-05 11:37:061415parcourir

    Introduction

    Qu'est-ce que le rappel ? Pour faire simple, le rappel est une notification de rappel. Lorsque nous devons notifier certaines tâches après qu'une méthode est terminée ou qu'un événement est déclenché, nous devons utiliser le rappel.

    La langue dans laquelle vous êtes le plus susceptible de voir le rappel est javascript. Fondamentalement, en javascript, le rappel est partout. Afin de résoudre le problème de l'enfer des rappels provoqué par les rappels, des promesses ont été spécialement introduites dans ES6 pour résoudre ce problème.

    Afin de faciliter l'interaction avec les méthodes natives, JNA propose également Callback pour le rappel. L'essence d'un rappel dans JNA est un pointeur vers une fonction native. Grâce à ce pointeur, les méthodes de la fonction native peuvent être appelées.

    Callback dans JNA

    Regardons d'abord la définition du Callback dans 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"));
    }

    Toutes les méthodes de Callback doivent implémenter cette interface de Callback. L'interface Callback est très simple. Elle définit une interface et deux propriétés.

    Regardons d'abord cette interface. Le nom de l'interface est UncaughtExceptionHandler, et elle contient une méthode uncaughtException. Cette interface est principalement utilisée pour gérer les exceptions qui ne sont pas capturées dans le code de rappel de JAVA.

    Notez que dans la méthode uncaughtException, les exceptions ne peuvent pas être levées et toutes les exceptions levées à partir de cette méthode seront ignorées.

    METHOD_NAME Ce champ spécifie la méthode à appeler par Callback.

    S'il n'y a qu'une seule méthode publique définie dans la classe Callback, alors la méthode de rappel par défaut est cette méthode. Si plusieurs méthodes publiques sont définies dans la classe Callback, la méthode avec METHOD_NAME = "callback" sera sélectionnée comme rappel.

    Le dernier attribut est FORBIDDEN_NAMES. Indique que les noms de cette liste ne peuvent pas être utilisés comme méthodes de rappel.

    Actuellement, il semble qu'il existe trois noms de méthodes qui ne peuvent pas être utilisés, à savoir : "hashCode", "equals" et "toString".

    Callback a également un frère appelé DLLCallback. Jetons un coup d'œil à la définition de DLLCallback :

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

    DLLCallback est principalement utilisé pour accéder à l'API Windows.

    Pour les objets de rappel, nous devons être responsables de la libération des objets de rappel nous-mêmes. Si le code natif tente d’accéder à un rappel recyclé, cela peut provoquer le crash de la VM.

    Application du rappel

    Définition du rappel

    Parce que le rappel dans JNA mappe en fait le pointeur vers la fonction en natif. Tout d'abord, jetez un œil aux pointeurs de fonction définis dans la structure :

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

    Dans cette structure, deux pointeurs de fonction sont définis, avec respectivement deux paramètres et un paramètre.

    La définition de rappel JNA correspondante est la suivante :

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

    Nous définissons deux interfaces dans Structure qui héritent de Callback, et la méthode d'invocation correspondante est définie dans l'interface correspondante.

    Ensuite, jetez un œil à la méthode d'appel spécifique :

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

    De plus, Callback peut également être utilisé comme valeur de retour de la fonction, comme indiqué ci-dessous :

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

    Pour ce pointeur de fonction distinct, nous devons personnaliser une bibliothèque et définissez-le dedans Le rappel correspondant est le suivant :

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

    Acquisition et application du rappel

    Si le rappel est défini dans Structure, il peut être automatiquement instancié lorsque la Structure est initialisée, et il vous suffit alors d'accéder aux propriétés correspondantes depuis Structure.

    Si le rappel est défini dans une bibliothèque ordinaire, il est le suivant :

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

    Dans l'exemple ci-dessus, nous avons défini deux rappels dans une bibliothèque, l'un est un rappel sans valeur de retour et l'autre est un rappel qui renvoie octet .

    JNA fournit une classe d'outils simple pour nous aider à obtenir le rappel. Cette classe d'outils est CallbackReference, et la méthode correspondante est CallbackReference.getCallback, comme indiqué ci-dessous :

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

    Le résultat de sortie est le suivant :

    INFO com.flydean. .CallbackUsage - cbV1 : interface proxy vers la fonction native @0xffffffffc46eeefc (com.flydean.CallbackUsage$TestLibrary$VoidCallback)
    INFO com.flydean.CallbackUsage - cbB1 : interface proxy vers la fonction native @0xffffffffc46eeefc (com.flydean.CallbackUsage$TestLibrary$ByteC retour en arrière )

    On voit que ces deux Callbacks sont en fait des proxys pour les méthodes natives. Si vous regardez la logique d'implémentation de getCallback en détail :

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

    Vous pouvez voir que sa logique d'implémentation consiste d'abord à déterminer si le type est une interface. S'il ne s'agit pas d'une interface, une erreur sera signalée. Déterminez ensuite s’il s’agit d’un mappage direct. En fait, l'implémentation actuelle de JNA est entièrement le mappage d'interface, donc la logique suivante est d'obtenir le rappel correspondant au pointeur de fonction à partir de pointerCallbackMap. Recherchez ensuite le rappel spécifique en fonction du type transmis.

    S'il n'est pas trouvé, créez un nouveau rappel et enfin stockez le rappel nouvellement créé dans pointerCallbackMap.

    Tout le monde doit noter qu'il existe ici un paramètre clé appelé Pointeur. Lorsque vous l'utilisez réellement, vous devez transmettre un pointeur vers la véritable fonction naitve. Dans l'exemple ci-dessus, par souci de simplicité, nous avons personnalisé un pointeur, ce qui n'a pas beaucoup de signification pratique.

    如果真的要想在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);

    Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

    Déclaration:
    Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer