


Comment résoudre le problème de rappel dans JNA lors d'une utilisation avancée de Java
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!

Outils d'IA chauds

Undresser.AI Undress
Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover
Outil d'IA en ligne pour supprimer les vêtements des photos.

Undress AI Tool
Images de déshabillage gratuites

Clothoff.io
Dissolvant de vêtements AI

AI Hentai Generator
Générez AI Hentai gratuitement.

Article chaud

Outils chauds

Bloc-notes++7.3.1
Éditeur de code facile à utiliser et gratuit

SublimeText3 version chinoise
Version chinoise, très simple à utiliser

Envoyer Studio 13.0.1
Puissant environnement de développement intégré PHP

Dreamweaver CS6
Outils de développement Web visuel

SublimeText3 version Mac
Logiciel d'édition de code au niveau de Dieu (SublimeText3)

Guide de la racine carrée en Java. Nous discutons ici du fonctionnement de Square Root en Java avec un exemple et son implémentation de code respectivement.

Guide du nombre parfait en Java. Nous discutons ici de la définition, comment vérifier le nombre parfait en Java ?, des exemples d'implémentation de code.

Guide du numéro Armstrong en Java. Nous discutons ici d'une introduction au numéro d'Armstrong en Java ainsi que d'une partie du code.

Guide du générateur de nombres aléatoires en Java. Nous discutons ici des fonctions en Java avec des exemples et de deux générateurs différents avec d'autres exemples.

Guide de Weka en Java. Nous discutons ici de l'introduction, de la façon d'utiliser Weka Java, du type de plate-forme et des avantages avec des exemples.

Guide du nombre de Smith en Java. Nous discutons ici de la définition, comment vérifier le numéro Smith en Java ? exemple avec implémentation de code.

Dans cet article, nous avons conservé les questions d'entretien Java Spring les plus posées avec leurs réponses détaillées. Pour que vous puissiez réussir l'interview.

Java 8 présente l'API Stream, fournissant un moyen puissant et expressif de traiter les collections de données. Cependant, une question courante lors de l'utilisation du flux est: comment se casser ou revenir d'une opération FOREAK? Les boucles traditionnelles permettent une interruption ou un retour précoce, mais la méthode Foreach de Stream ne prend pas directement en charge cette méthode. Cet article expliquera les raisons et explorera des méthodes alternatives pour la mise en œuvre de terminaison prématurée dans les systèmes de traitement de flux. Lire plus approfondie: Améliorations de l'API Java Stream Comprendre le flux Forach La méthode foreach est une opération terminale qui effectue une opération sur chaque élément du flux. Son intention de conception est
