Utiliser JNI au lieu de JNA pour appeler du code natif?


115

JNA semble un peu plus facile à utiliser pour appeler du code natif par rapport à JNI. Dans quels cas utiliseriez-vous JNI sur JNA?


Un facteur important pour lequel nous avons choisi JNA plutôt que JNI, est que JNA n'a nécessité aucune modification de nos bibliothèques natives. Devoir personnaliser des bibliothèques natives juste pour pouvoir travailler avec Java était un grand NON pour nous.
kvr du

Réponses:


126
  1. JNA ne prend pas en charge le mappage des classes C ++, donc si vous utilisez la bibliothèque C ++, vous aurez besoin d'un wrapper jni
  2. Si vous avez besoin de beaucoup de copie de mémoire. Par exemple, vous appelez une méthode qui vous renvoie un grand tampon d'octets, vous changez quelque chose dedans, puis vous devez appeler une autre méthode qui utilise ce tampon d'octets. Cela vous obligerait à copier ce tampon de c vers java, puis à le recopier de java vers c. Dans ce cas, jni gagnera en performances car vous pouvez conserver et modifier ce tampon en c, sans copier.

Ce sont les problèmes que j'ai rencontrés. Il y a peut-être plus. Mais en général, les performances ne sont pas si différentes entre jna et jni, donc partout où vous pouvez utiliser JNA, utilisez-le.

ÉDITER

Cette réponse semble être très populaire. Voici donc quelques ajouts:

  1. Si vous avez besoin de mapper C ++ ou COM, il existe une bibliothèque d'Oliver Chafic, créateur de JNAerator, appelée BridJ . C'est encore une jeune bibliothèque, mais elle possède de nombreuses fonctionnalités intéressantes:
    • Interopérabilité dynamique C / C ++ / COM: appelez des méthodes C ++, créez des objets C ++ (et sous-classez des classes C ++ à partir de Java!)
    • Mappages de types simples avec une bonne utilisation des génériques (y compris un modèle beaucoup plus agréable pour les pointeurs)
    • Prise en charge complète de JNAerator
    • fonctionne sur Windows, Linux, MacOS X, Solaris, Android
  2. En ce qui concerne la copie de mémoire, je crois que JNA prend en charge les ByteBuffers directs, de sorte que la copie de mémoire peut être évitée.

Donc, je crois toujours que dans la mesure du possible, il est préférable d'utiliser JNA ou BridJ, et de revenir à jni si les performances sont critiques, car si vous devez appeler fréquemment des fonctions natives, une baisse des performances est perceptible.


6
Je ne suis pas d'accord, la JNA a beaucoup de frais généraux. Bien que sa commodité vaut la peine d'être utilisée dans du code non critique dans le temps
Gregory Pakosz

3
Je vous conseille de faire preuve de prudence avant d'utiliser BridJ pour un projet Android, de citer directement à partir de sa page de téléchargement : "BridJ fonctionne partiellement sur les émulateurs Android / arm (avec le SDK), et probablement même sur des appareils réels (non testés). "
kizzx2

1
@StockB: si vous avez une bibliothèque avec api où vous devez passer des objets C ++, ou appeler des méthodes sur un objet C ++, JNA ne peut pas le faire. Il ne peut appeler que des méthodes C vanille.
Denis Tulskiy

2
Autant que je sache, JNI ne peut appeler que des JNIEXPORTfonctions globales . J'explore actuellement JavaCpp en tant qu'option, qui utilise JNI, mais je ne pense pas que vanilla JNI le supporte. Existe-t-il un moyen d'appeler des fonctions membres C ++ en utilisant le JNI vanilla que je néglige?
StockB


29

Il est difficile de répondre à une question aussi générique. Je suppose que la différence la plus évidente est qu'avec JNI, la conversion de type est implémentée du côté natif de la frontière Java / native, tandis qu'avec JNA, la conversion de type est implémentée en Java. Si vous vous sentez déjà assez à l'aise avec la programmation en C et que vous devez implémenter vous-même du code natif, je suppose que JNI ne semble pas trop complexe. Si vous êtes un programmeur Java et avez seulement besoin d'appeler une bibliothèque native tierce, l'utilisation de JNA est probablement le chemin le plus simple pour éviter les problèmes peut-être pas si évidents avec JNI.

Bien que je n'ai jamais évalué de différences, je le ferais à cause de la conception, au moins supposons que la conversion de type avec JNA dans certaines situations fonctionnera moins bien qu'avec JNI. Par exemple, lors du passage de tableaux, JNA les convertira de Java en natif au début de chaque appel de fonction et de nouveau à la fin de l'appel de fonction. Avec JNI, vous pouvez vous contrôler lorsqu'une "vue" native du tableau est générée, ne créant potentiellement qu'une vue d'une partie du tableau, gardez la vue sur plusieurs appels de fonction et à la fin, libérez la vue et décidez si vous voulez pour conserver les modifications (nécessitant éventuellement de recopier les données) ou annuler les modifications (aucune copie requise). Je sais que vous pouvez utiliser un tableau natif pour les appels de fonction avec JNA en utilisant la classe Memory, mais cela nécessitera également une copie de la mémoire, ce qui peut être inutile avec JNI. La différence n'est peut-être pas pertinente, mais si votre objectif initial est d'augmenter les performances de l'application en en implémentant certaines parties dans du code natif, l'utilisation d'une technologie de pont moins performante ne semble pas être le choix le plus évident.


8
  1. Vous écrivez du code il y a quelques années avant le JNA ou vous ciblez un JRE pré-1.4.
  2. Le code avec lequel vous travaillez n'est pas dans un DLL \ SO.
  3. Vous travaillez sur du code incompatible avec LGPL.

Ce n'est que ce que je peux trouver du haut de ma tête, même si je ne suis pas un grand utilisateur de l'un ou de l'autre. Il semble également que vous pourriez éviter JNA si vous vouliez une meilleure interface que celle qu'ils fournissent, mais vous pouvez coder autour de cela en java.


9
Je ne suis pas d'accord sur 2 - la conversion d'une bibliothèque statique en une bibliothèque dynamique est facile. Voir ma question abput it stackoverflow.com/questions/845183/...
jb.

3
À partir de JNA 4.0, JNA possède une double licence sous LGPL 2.1 et Apache License 2.0, et c'est à vous de choisir
sbarber2

8

D'ailleurs, dans l'un de nos projets, nous avons gardé une très petite empreinte JNI. Nous avons utilisé des tampons de protocole pour représenter nos objets de domaine et n'avions donc qu'une seule fonction native pour relier Java et C (alors bien sûr, cette fonction C appellerait un tas d'autres fonctions).


5
Donc, au lieu d'un appel de méthode, nous avons un message qui passe. J'ai investi pas mal de temps dans JNI et JNA et aussi BridJ, mais très vite, ils deviennent tous un peu trop effrayants.
Ustaman Sangat

5

Ce n'est pas une réponse directe et je n'ai aucune expérience avec JNA mais, quand je regarde les projets utilisant JNA et que je vois des noms comme SVNKit, IntelliJ IDEA, NetBeans IDE, etc., j'ai tendance à croire que c'est une bibliothèque assez décente.

En fait, je pense vraiment que j'aurais utilisé JNA au lieu de JNI quand je devais le faire car cela semble en effet plus simple que JNI (qui a un processus de développement ennuyeux). Dommage, la JNA n'a pas été libérée à ce moment-là.


3

Si vous voulez des performances JNI mais que vous êtes découragé par sa complexité, vous pouvez envisager d'utiliser des outils qui génèrent automatiquement des liaisons JNI. Par exemple, JANET (avertissement: je l'ai écrit) vous permet de mélanger du code Java et C ++ dans un seul fichier source, et par exemple de faire des appels de C ++ vers Java en utilisant la syntaxe Java standard. Par exemple, voici comment imprimer une chaîne C sur la sortie standard Java:

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

JANET traduit ensuite le Java intégré à backtick en appels JNI appropriés.


3

J'ai fait quelques benchmarks simples avec JNI et JNA.

Comme d'autres l'ont déjà souligné, la JNA est pour des raisons de commodité. Vous n'avez pas besoin de compiler ou d'écrire du code natif lorsque vous utilisez JNA. Le chargeur de bibliothèque natif de JNA est également l'un des meilleurs / des plus faciles à utiliser que j'ai jamais vu. Malheureusement, vous ne pouvez pas l'utiliser pour JNI, semble-t-il. (C'est pourquoi j'ai écrit une alternative pour System.loadLibrary () qui utilise la convention de chemin de JNA et prend en charge le chargement transparent à partir du chemin de classe (c.-à-d. Jars ).)

Les performances de JNA peuvent cependant être bien pires que celles de JNI. J'ai fait un test très simple qui appelait une fonction d'incrémentation d'entier natif simple "return arg + 1;". Les tests de performance effectués avec jmh ont montré que les appels JNI à cette fonction sont 15 fois plus rapides que JNA.

Un exemple plus "complexe" où la fonction native résume un tableau d'entiers de 4 valeurs montre toujours que les performances JNI sont 3 fois plus rapides que JNA. L'avantage réduit était probablement dû à la façon dont vous accédez aux tableaux dans JNI: mon exemple a créé des éléments et les a relâchés à chaque opération de sommation.

Le code et les résultats des tests peuvent être trouvés sur github .


2

J'ai étudié JNI et JNA pour comparer les performances car nous devions décider que l'un d'entre eux appelle une dll dans le projet et nous avions une contrainte de temps réel. Les résultats ont montré que JNI a de meilleures performances que JNA (environ 40 fois). Il existe peut-être une astuce pour de meilleures performances dans JNA, mais elle est très lente pour un exemple simple.


1

À moins que je ne manque quelque chose, la principale différence entre JNA et JNI n'est-elle pas qu'avec JNA, vous ne pouvez pas appeler du code Java à partir du code natif (C)?


6
Vous pouvez. Avec une classe de rappel qui correspond à un pointeur de fonction du côté C. void register_callback (void (*) (const int)); serait mappé sur public statique natif void register_callback (MyCallback arg1); où MyCallback est une interface étendant com.sun.jna.Callback avec une seule méthode void apply (valeur int);
Ustaman Sangat

@Augusto, cela peut également être un commentaire s'il vous plaît
swiftBoy

0

Dans mon application spécifique, JNI s'est avéré beaucoup plus facile à utiliser. J'avais besoin de lire et d'écrire des flux continus vers et depuis un port série - et rien d'autre. Plutôt que d'essayer d'apprendre l'infrastructure très impliquée dans JNA, j'ai trouvé beaucoup plus facile de prototyper l'interface native de Windows avec une DLL spéciale qui n'exportait que six fonctions:

  1. DllMain (requis pour l'interface avec Windows)
  2. OnLoad (fait juste un OutputDebugString pour que je puisse savoir quand le code Java se joint)
  3. OnUnload (idem)
  4. Open (ouvre le port, démarre les threads de lecture et d'écriture)
  5. QueueMessage (met les données en file d'attente pour la sortie par le thread d'écriture)
  6. GetMessage (attend et renvoie les données reçues par le thread de lecture depuis le dernier appel)
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.