Heim >Java >javaLernprogramm >So lösen Sie das Rückrufproblem in JNA bei fortgeschrittener Java-Nutzung
Was ist Rückruf? Vereinfacht ausgedrückt handelt es sich bei einem Rückruf um eine Rückrufbenachrichtigung. Wenn wir bestimmte Aufgaben benachrichtigen müssen, nachdem eine Methode abgeschlossen oder ein Ereignis ausgelöst wurde, müssen wir einen Rückruf verwenden.
Die Sprache, in der Sie Rückrufe am wahrscheinlichsten sehen, ist Javascript. Grundsätzlich ist Rückruf in Javascript überall. Um das durch Rückrufe verursachte Problem der Rückrufhölle zu lösen, wurden in ES6 speziell Versprechen eingeführt, um dieses Problem zu lösen.
Um die Interaktion mit nativen Methoden zu erleichtern, bietet JNA auch Callback für Rückrufe an. Das Wesentliche eines Rückrufs in JNA ist ein Zeiger auf eine native Funktion. Über diesen Zeiger können Methoden in der nativen Funktion aufgerufen werden.
Sehen wir uns zunächst die Definition von Callback in JNA an:
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")); }
Alle Callback-Methoden müssen diese Callback-Schnittstelle implementieren. Die Callback-Schnittstelle ist sehr einfach. Sie definiert eine Schnittstelle und zwei Eigenschaften.
Werfen wir zunächst einen Blick auf diese Schnittstelle. Der Name der Schnittstelle ist UncaughtExceptionHandler und sie enthält eine uncaughtException-Methode. Diese Schnittstelle wird hauptsächlich zur Behandlung von Ausnahmen verwendet, die nicht im Rückrufcode von JAVA abgefangen werden.
Beachten Sie, dass in der uncaughtException-Methode keine Ausnahmen ausgelöst werden können und alle von dieser Methode ausgelösten Ausnahmen ignoriert werden.
METHOD_NAME Dieses Feld gibt die Methode an, die von Callback aufgerufen werden soll.
Wenn in der Callback-Klasse nur eine öffentliche Methode definiert ist, ist diese Methode die Standard-Callback-Methode. Wenn in der Callback-Klasse mehrere öffentliche Methoden definiert sind, wird die Methode mit METHOD_NAME = „callback“ als Callback ausgewählt.
Das letzte Attribut ist FORBIDDEN_NAMES. Gibt an, dass die Namen in dieser Liste nicht als Rückrufmethoden verwendet werden können.
Derzeit scheint es drei Methodennamen zu geben, die nicht verwendet werden können, nämlich: „hashCode“, „equals“ und „toString“.
Callback hat auch einen Geschwister namens DLLCallback. Werfen wir einen Blick auf die Definition von DLLCallback:
public interface DLLCallback extends Callback { @java.lang.annotation.Native int DLL_FPTRS = 16; }
DLLCallback wird hauptsächlich für den Zugriff auf die Windows-API verwendet.
Bei Callback-Objekten müssen wir selbst für die Freigabe der Callback-Objekte verantwortlich sein. Wenn nativer Code versucht, auf einen recycelten Rückruf zuzugreifen, kann dies zum Absturz der VM führen.
Weil der Rückruf in JNA tatsächlich den Zeiger auf die Funktion in nativer Form zuordnet. Schauen Sie sich zunächst die in der Struktur definierten Funktionszeiger an:
struct _functions { int (*open)(const char*,int); int (*close)(int); };
In dieser Struktur sind zwei Funktionszeiger definiert, mit zwei Parametern bzw. einem Parameter.
Die entsprechende JNA-Callback-Definition lautet wie folgt:
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; }
Wir definieren in Structure zwei Schnittstellen, die von Callback erben, und die entsprechende Aufrufmethode ist in der entsprechenden Schnittstelle definiert.
Dann werfen Sie einen Blick auf die spezifische Aufrufmethode:
Functions funcs = new Functions(); lib.init(funcs); int fd = funcs.open.invoke("myfile", 0); funcs.close.invoke(fd);
Darüber hinaus kann Callback auch als Rückgabewert der Funktion verwendet werden, wie unten gezeigt:
typedef void (*sig_t)(int); sig_t signal(int signal, sig_t sigfunc);
Für diesen separaten Funktionszeiger müssen wir eine Bibliothek anpassen und definieren Sie es darin. Der entsprechende Rückruf lautet wie folgt:
public interface CLibrary extends Library { public interface SignalFunction extends Callback { void invoke(int signal); } SignalFunction signal(int signal, SignalFunction func); }
Wenn der Rückruf in der Struktur definiert ist, kann er bei der Initialisierung der Struktur automatisch instanziiert werden, und Sie müssen dann nur noch auf die entsprechenden Eigenschaften zugreifen Struktur. Kann.
Wenn sich die Rückrufdefinition in einer gewöhnlichen Bibliothek befindet, lautet sie wie folgt:
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); }
Im obigen Beispiel haben wir zwei Rückrufe in einer Bibliothek definiert, einer ist ein Rückruf ohne Rückgabewert und der andere ist ein Rückruf, der zurückgibt Byte.
JNA stellt eine einfache Toolklasse bereit, die uns beim Abrufen von Callback hilft. Die entsprechende Methode ist CallbackReference.getCallback, wie unten gezeigt:
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);
Das Ausgabeergebnis lautet wie folgt:
INFO com.flydean .CallbackUsage – cbV1:Proxy-Schnittstelle zur nativen Funktion@0xffffffffc46eeefc (com.flydean.CallbackUsage$TestLibrary$VoidCallback)
INFO com.flydean.CallbackUsage – cbB1:Proxy-Schnittstelle zur nativen Funktion@0xffffffffc46eeefc. (com.flydean.CallbackUsage$TestLib rary$ByteCallback )
Es ist ersichtlich, dass diese beiden Rückrufe tatsächlich Proxys für native Methoden sind. Wenn Sie sich die Implementierungslogik von getCallback im Detail ansehen:
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; } }
Sie können sehen, dass die Implementierungslogik zunächst ermittelt, ob es sich bei dem Typ um eine Schnittstelle handelt. Wenn es sich nicht um eine Schnittstelle handelt, wird ein Fehler gemeldet. Stellen Sie dann fest, ob es sich um eine direkte Zuordnung handelt. Tatsächlich besteht die aktuelle Implementierung von JNA ausschließlich aus Schnittstellenzuordnungen. Daher besteht die nächste Logik darin, den dem Funktionszeiger entsprechenden Rückruf von pointerCallbackMap abzurufen. Suchen Sie dann den spezifischen Rückruf entsprechend dem übergebenen Typ.
Wenn es nicht gefunden wird, erstellen Sie einen neuen Rückruf und speichern Sie schließlich den neu erstellten Rückruf in pointerCallbackMap.
Jeder sollte beachten, dass es hier einen Schlüsselparameter namens Pointer gibt. Wenn Sie ihn tatsächlich verwenden, müssen Sie einen Zeiger auf die echte Naivitätsfunktion übergeben. Im obigen Beispiel haben wir der Einfachheit halber einen Zeiger angepasst, der keine große praktische Bedeutung hat.
如果真的要想在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);
Das obige ist der detaillierte Inhalt vonSo lösen Sie das Rückrufproblem in JNA bei fortgeschrittener Java-Nutzung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!