Verwenden Sie JNI anstelle von JNA, um nativen Code aufzurufen?

115

JNA scheint im Vergleich zu JNI ein bisschen einfacher zu verwenden zu sein, um nativen Code aufzurufen. In welchen Fällen würden Sie JNI über JNA verwenden?

Marcus Leon
quelle
Ein wichtiger Faktor, für den wir uns für JNA gegenüber JNI entschieden haben, ist, dass für JNA keine Änderungen an unseren nativen Bibliotheken erforderlich waren. Native Bibliotheken anpassen zu müssen, um mit Java arbeiten zu können, war für uns ein großes NEIN.
kvr

Antworten:

126
  1. JNA unterstützt keine Zuordnung von C ++ - Klassen. Wenn Sie also eine C ++ - Bibliothek verwenden, benötigen Sie einen JNI-Wrapper
  2. Wenn Sie viel Speicher kopieren müssen. Wenn Sie beispielsweise eine Methode aufrufen, die Ihnen einen großen Bytepuffer zurückgibt, etwas darin ändern, müssen Sie eine andere Methode aufrufen, die diesen Bytepuffer verwendet. Dazu müssten Sie diesen Puffer von c nach Java kopieren und dann von Java nach c zurückkopieren. In diesem Fall gewinnt jni an Leistung, da Sie diesen Puffer in c behalten und ändern können, ohne ihn zu kopieren.

Dies sind die Probleme, auf die ich gestoßen bin. Vielleicht gibt es noch mehr. Aber im Allgemeinen unterscheidet sich die Leistung nicht so stark zwischen jna und jni. Wo immer Sie JNA verwenden können, verwenden Sie es.

BEARBEITEN

Diese Antwort scheint sehr beliebt zu sein. Hier sind einige Ergänzungen:

  1. Wenn Sie C ++ oder COM zuordnen müssen, gibt es eine Bibliothek von Oliver Chafic, dem Erfinder von JNAerator, namens BridJ . Es ist noch eine junge Bibliothek, hat aber viele interessante Eigenschaften:
    • Dynamisches C / C ++ / COM-Interop: C ++ - Methoden aufrufen, C ++ - Objekte erstellen (und C ++ - Klassen der Unterklasse aus Java!)
    • Einfache Typzuordnungen mit guter Verwendung von Generika (einschließlich eines viel schöneren Modells für Zeiger)
    • Volle JNAerator-Unterstützung
    • funktioniert unter Windows, Linux, MacOS X, Solaris, Android
  2. Was das Kopieren von Speicher betrifft, glaube ich, dass JNA direkte ByteBuffer unterstützt, sodass das Kopieren von Speicher vermieden werden kann.

Daher glaube ich immer noch, dass es nach Möglichkeit besser ist, JNA oder BridJ zu verwenden und zu jni zurückzukehren, wenn die Leistung kritisch ist, denn wenn Sie häufig native Funktionen aufrufen müssen, ist ein Leistungseinbruch spürbar.

Denis Tulskiy
quelle
6
Ich bin anderer Meinung, JNA hat viel Aufwand. Obwohl seine Bequemlichkeit es wert ist, in nicht zeitkritischem Code verwendet zu werden
Gregory Pakosz
3
Ich rate zur Vorsicht, bevor Sie BridJ für ein Android-Projekt verwenden, um direkt von der Download-Seite zu zitieren : "BridJ funktioniert teilweise auf Android / Arm-Emulatoren (mit dem SDK) und wahrscheinlich sogar auf tatsächlichen Geräten (ungetestet). "
kizzx2
1
@StockB: Wenn Sie eine Bibliothek mit API haben, in der Sie C ++ - Objekte übergeben oder Methoden für C ++ - Objekte aufrufen müssen, kann JNA dies nicht. Es können nur Vanilla C-Methoden aufgerufen werden.
Denis Tulskiy
2
Soweit ich weiß, kann JNI nur globale JNIEXPORTFunktionen aufrufen . Ich untersuche derzeit JavaCpp als Option, die JNI verwendet, aber ich glaube nicht, dass Vanilla JNI dies unterstützt. Gibt es eine Möglichkeit, C ++ - Mitgliedsfunktionen mit Vanilla JNI aufzurufen, die ich übersehen habe?
StockB
1
Bitte werfen Sie einen Blick hier stackoverflow.com/questions/20332472/catch-a-double-hotkey
ア レ ッ ク ス
29

Es ist schwierig, eine so allgemeine Frage zu beantworten. Ich nehme an, der offensichtlichste Unterschied besteht darin, dass mit JNI die Typkonvertierung auf der nativen Seite der Java / Native-Grenze implementiert wird, während mit JNA die Typkonvertierung in Java implementiert wird. Wenn Sie sich mit dem Programmieren in C bereits recht wohl fühlen und selbst nativen Code implementieren müssen, würde ich davon ausgehen, dass JNI nicht zu komplex erscheint. Wenn Sie ein Java-Programmierer sind und nur eine native Bibliothek eines Drittanbieters aufrufen müssen, ist die Verwendung von JNA wahrscheinlich der einfachste Weg, um die möglicherweise nicht so offensichtlichen Probleme mit JNI zu vermeiden.

Obwohl ich noch nie Unterschiede gemessen habe, würde ich aufgrund des Designs zumindest annehmen, dass die Typkonvertierung mit JNA in einigen Situationen schlechter abschneidet als mit JNI. Wenn Sie beispielsweise Arrays übergeben, konvertiert JNA diese zu Beginn jedes Funktionsaufrufs von Java in native und am Ende des Funktionsaufrufs zurück. Mit JNI können Sie selbst steuern, wann eine native "Ansicht" des Arrays generiert wird. Möglicherweise wird nur eine Ansicht eines Teils des Arrays erstellt, die Ansicht über mehrere Funktionsaufrufe hinweg beibehalten und am Ende die Ansicht freigegeben und entschieden, ob Sie möchten um die Änderungen beizubehalten (möglicherweise müssen die Daten zurückkopiert werden) oder die Änderungen zu verwerfen (keine Kopie erforderlich). Ich weiß, dass Sie ein natives Array für Funktionsaufrufe mit JNA mithilfe der Speicherklasse verwenden können, dies erfordert jedoch auch das Kopieren des Speichers. Dies kann bei JNI unnötig sein. Der Unterschied ist möglicherweise nicht relevant, aber wenn Ihr ursprüngliches Ziel darin besteht, die Anwendungsleistung durch Implementierung von Teilen davon in nativem Code zu steigern, scheint die Verwendung einer Bridge-Technologie mit schlechterer Leistung nicht die naheliegendste Wahl zu sein.

jarnbjo
quelle
8
  1. Sie schreiben Code vor einigen Jahren, bevor es JNA gab, oder zielen auf eine JRE vor 1.4 ab.
  2. Der Code, mit dem Sie arbeiten, befindet sich nicht in einer DLL \ SO.
  3. Sie arbeiten an Code, der nicht mit LGPL kompatibel ist.

Das ist nur das, was ich mir auf den ersten Blick einfallen lassen kann, obwohl ich auch kein schwerer Benutzer bin. Es scheint auch so, als könnten Sie JNA vermeiden, wenn Sie eine bessere Benutzeroberfläche als die von ihnen bereitgestellte wünschen, aber Sie könnten dies in Java umgehen.

Steinmetall
quelle
9
Ich bin nicht einverstanden mit 2 - die Konvertierung der statischen Bibliothek in die dynamische Bibliothek ist einfach. Siehe meine Frage abput it stackoverflow.com/questions/845183/…
jb.
3
Ab JNA 4.0 ist JNA sowohl unter LGPL 2.1 als auch unter Apache License 2.0 doppelt lizenziert. Die Wahl liegt bei Ihnen
sbarber2
8

Übrigens haben wir in einem unserer Projekte einen sehr kleinen JNI-Fußabdruck beibehalten. Wir haben Protokollpuffer zur Darstellung unserer Domänenobjekte verwendet und hatten daher nur eine native Funktion, um Java und C zu überbrücken (dann würde diese C-Funktion natürlich eine Reihe anderer Funktionen aufrufen).

Ustaman Sangat
quelle
5
Anstelle eines Methodenaufrufs wird also eine Nachricht übergeben. Ich habe ziemlich viel Zeit in JNI und JNA und auch in BridJ investiert, aber ziemlich bald werden sie alle etwas zu beängstigend.
Ustaman Sangat
5

Es ist keine direkte Antwort und ich habe keine Erfahrung mit JNA, aber wenn ich mir die Projekte mit JNA anschaue ansehe und Namen wie SVNKit, IntelliJ IDEA, NetBeans IDE usw. sehe, glaube ich eher, dass es eine ziemlich anständige Bibliothek ist.

Eigentlich denke ich definitiv, dass ich JNA anstelle von JNI verwendet hätte, als ich musste, da es in der Tat einfacher aussieht als JNI (das einen langweiligen Entwicklungsprozess hat). Schade, JNA wurde zu diesem Zeitpunkt noch nicht veröffentlicht.

Pascal Thivent
quelle
3

Wenn Sie die JNI-Leistung wünschen, sich aber von ihrer Komplexität abschrecken lassen, können Sie Tools verwenden, die automatisch JNI-Bindungen generieren. Mit JANET (Haftungsausschluss: Ich habe es geschrieben) können Sie beispielsweise Java- und C ++ - Code in einer einzigen Quelldatei mischen und z. B. mit der Standard-Java-Syntax Aufrufe von C ++ nach Java tätigen. So drucken Sie beispielsweise eine C-Zeichenfolge in die Java-Standardausgabe:

native "C++" void printHello() {
    const char* helloWorld = "Hello, World!";
    `System.out.println(#$(helloWorld));`
}

JANET übersetzt dann das in Backticks eingebettete Java in die entsprechenden JNI-Aufrufe.

Dawid Kurzyniec
quelle
3

Ich habe tatsächlich einige einfache Benchmarks mit JNI und JNA durchgeführt.

Wie andere bereits betont haben, dient JNA der Bequemlichkeit. Sie müssen keinen nativen Code kompilieren oder schreiben, wenn Sie JNA verwenden. Der native Bibliothekslader von JNA ist auch einer der besten / am einfachsten zu verwendenden, die ich je gesehen habe. Leider können Sie es anscheinend nicht für JNI verwenden. (Deshalb habe ich eine Alternative für System.loadLibrary () geschrieben.) , die die von JNA verwendet und das nahtlose Laden aus dem Klassenpfad (dh Jars) unterstützt.)

Die Leistung von JNA kann jedoch viel schlechter sein als die von JNI. Ich habe einen sehr einfachen Test durchgeführt, der eine einfache native Ganzzahl-Inkrementfunktion "return arg + 1;" nannte. Mit jmh durchgeführte Benchmarks zeigten, dass JNI-Aufrufe dieser Funktion 15-mal schneller sind als JNA.

Ein "komplexeres" Beispiel, bei dem die native Funktion ein ganzzahliges Array von 4 Werten zusammenfasst, zeigte immer noch, dass die JNI-Leistung dreimal schneller ist als die von JNA. Der reduzierte Vorteil war wahrscheinlich darauf zurückzuführen, wie Sie in JNI auf Arrays zugreifen: In meinem Beispiel wurden einige Elemente erstellt und bei jedem Summierungsvorgang erneut freigegeben.

Code und Testergebnisse finden Sie bei github .

user1050755
quelle
2

Ich habe JNI und JNA zum Leistungsvergleich untersucht, weil wir uns für einen von ihnen entscheiden mussten, um eine DLL im Projekt aufzurufen, und wir hatten eine Echtzeitbeschränkung. Die Ergebnisse haben gezeigt, dass JNI eine höhere Leistung als JNA aufweist (ungefähr 40-mal). Vielleicht gibt es einen Trick für eine bessere Leistung in JNA, aber für ein einfaches Beispiel ist er sehr langsam.

Mustafa Kemal
quelle
1

Ist der Hauptunterschied zwischen JNA und JNI nicht, dass Sie mit JNA keinen Java-Code aus nativem (C) Code aufrufen können, es sei denn, ich vermisse etwas?

Augusto
quelle
6
Sie können. Mit einer Rückrufklasse, die einem Funktionszeiger auf der C-Seite entspricht. void register_callback (void (*) (const int)); würde der öffentlichen statischen nativen Leere zugeordnet werden register_callback (MyCallback arg1); Dabei ist MyCallback eine Schnittstelle, die com.sun.jna.Callback mit einer einzigen Methode erweitert. void apply (int value);
Ustaman Sangat
@ Augusto dies kann ein Kommentar auch bitte sein
swiftBoy
0

In meiner spezifischen Anwendung erwies sich JNI als weitaus einfacher zu verwenden. Ich musste fortlaufende Streams zu und von einer seriellen Schnittstelle lesen und schreiben - und sonst nichts. Anstatt zu versuchen, die sehr komplexe Infrastruktur in JNA zu erlernen, fand ich es viel einfacher, die native Benutzeroberfläche in Windows mit einer speziellen DLL zu prototypisieren, die nur sechs Funktionen exportierte:

  1. DllMain (erforderlich für die Schnittstelle mit Windows)
  2. OnLoad (führt nur einen OutputDebugString durch, damit ich weiß, wann Java-Code angehängt wird)
  3. OnUnload (dito)
  4. Öffnen (öffnet den Port, startet das Lesen und Schreiben von Threads)
  5. QueueMessage (Warteschlangendaten für die Ausgabe durch den Schreibthread)
  6. GetMessage (wartet auf Daten und gibt sie zurück, die der Lesethread seit dem letzten Aufruf empfangen hat)
Walter Oney
quelle