Learn · concepts
Qu'est-ce que le JNI ? Interface Native Java pour les moddeurs
L'interface native Java (JNI) est le pont intégré de Java pour appeler du code C/C++ depuis la JVM et inversement. C'est la méthode standard utilisée par un mod Minecraft pour charger une bibliothèque native.
Qu'est-ce que JNI en termes simples ?
L'interface native Java (JNI) est la partie de Java qui relie deux mondes : le bytecode géré sur la JVM et le code machine compilé écrit en C ou en C++. Une méthode reçoit le mot-clé native et n'a pas de corps, et au moment de l'exécution, la JVM l'associe à une fonction correspondante dans une bibliothèque native chargée. À partir de là, les deux côtés échangent des arguments et des valeurs de retour.
La frontière n'est pas unidirectionnelle. La fonction native reçoit un pointeur JNIEnv, et cette poignée est la passerelle de retour vers Java : elle expose des appels pour lire et écrire des champs Java, invoquer des méthodes Java, construire des objets et lever des exceptions. Ainsi, le même pont qui permet à Java d'appeler vers l'extérieur permet également à C d'appeler vers l'intérieur.
Pourquoi un mod ou un client Minecraft toucherait-il au code natif ?
La plupart des programmes Java n'ont jamais besoin de JNI. Un mod y a recours lorsque la JVM seule ne peut pas faire le travail, généralement pour les graphiques, l'audio, l'intégration du système d'exploitation ou une routine qui doit être rapide. La bibliothèque native effectue le travail lourd et JNI est la méthode standard pour la charger et l'appeler.
Réutiliser une bibliothèque C éprouvée
Une base de code native et mature résout déjà le problème. Le réécrire en Java serait une perte de temps, c'est pourquoi le module s'y lie plutôt.
Atteindre le système d'exploitation
Certaines fonctionnalités de la plateforme n'ont pas d'API Java pure. Un appel natif devient alors le seul moyen d'y accéder.
Une voie brûlante
Une routine numérique étroite ou de bas niveau peut s'exécuter plus rapidement en tant que code natif compilé qu'en tant que bytecode sur la JVM.
Piloter les matériels et les pilotes
Le rendu, l'audio et l'accès aux périphériques se trouvent souvent derrière les bibliothèques natives que JNI expose au code Java.
Comment fonctionne un appel JNI de bout en bout ?
Le flux est identique sur toutes les plateformes : déclarez la méthode en Java, compilez une fonction C correspondante en une bibliothèque partagée, chargez-la au moment de l'exécution, laissez la JVM les lier par nom, puis appelez à travers. Les noms de fonction suivent un schéma fixe afin que la JVM puisse les résoudre, et un fichier d'en-tête généré précise la signature exacte à implémenter.
| Étape | Ce qui se passe |
|---|---|
| Déclaration | Une méthode Java est écrite avec le mot-clé native et laissée vide du côté Java. |
| Construction | Une fonction C ou C++ est compilée en une bibliothèque partagée : .dll sur Windows, .so sur Linux, .dylib sur macOS. |
| Chargement | Java appelle System.loadLibrary pour charger cette bibliothèque pendant l'exécution du programme. |
| Liaison | La JVM associe chaque méthode native à sa fonction C par une signature déformée par le nom. |
| Appel | Java invoque la méthode ; le contrôle passe au code natif et revient avec un résultat. |
Quel est le coût de JNI pour vous ?
JNI est puissant, mais il a des arêtes vives. Le code natif s’exécutant en dehors du filet de sécurité de la JVM, une erreur entraîne l’arrêt de l’ensemble du processus plutôt que de déclencher une exception capturable. La mémoire allouée du côté natif est invisible pour le ramasse-miettes, de sorte que les fuites s’accumulent à moins qu’elles ne soient libérées manuellement. Chaque passage entraîne une surcharge, ce qui rend coûteux l’appel à travers la limite dans une boucle serrée. Et les constructions natives sont verrouillées à une plateforme : une DLL Windows ne se chargera pas sous Linux sans une compilation séparée.
Ce que ça procure
- Accès direct aux bibliothèques C et C++ existantes
- Un moyen d'accéder aux fonctionnalités du système d'exploitation et du matériel que la JVM ne peut atteindre
- Exécution à la vitesse native pour les routines les plus utilisées
Ce que ça coûte
- Un crash natif peut faire tomber toute la JVM
- Gestion manuelle de la mémoire sans filet de sécurité du ramasse-miettes
- Builds spécifiques à chaque plateforme à maintenir et à distribuer
- Surcharge inter-domaines dans les boucles critiques
JNI face à l'API des fonctions et de la mémoire étrangères
JNI est le mécanisme original, mais il n'est plus le seul. Les versions récentes de Java incluent l'API des fonctions et de la mémoire étrangères (FFM), conçue pour appeler le code natif avec moins de code répétitif et des garanties de sécurité plus strictes. JNI domine toujours les chaînes d'outils existantes car il est omniprésent et parfaitement compris.
| JNI | API FFM | |
|---|---|---|
| Âge | Original, présent depuis les débuts de Java | Moderne, ajoutée dans les versions récentes |
| Code répétitif | En-têtes générés, fonctions C à noms modifiés | Handles de méthodes définis en Java, pas de code C intermédiaire |
| Sécurité de la mémoire | Manuelle ; les erreurs peuvent corrompre la JVM | Segments limités avec des vérifications plus robustes |
| Adoption | Omniprésente dans les bibliothèques existantes | En croissance, dans les nouvelles bases de code |
FAQ
Il est intégré. JNI est fourni avec le JDK et la JVM, donc le côté Java n'a besoin de rien de plus. La compilation de la partie native nécessite toujours une chaîne d'outils C ou C++ pour chaque plateforme cible.
Non. La partie Java reste portable, mais une bibliothèque native est compilée pour un système d'exploitation et un processeur. La distribution multiplateforme implique de créer et d'inclure un fichier .dll, .so et .dylib distinct.
Non. JNI est le mécanisme original. Les versions récentes de Java ajoutent l'API Foreign Function and Memory, qui vise à être plus sûre et à nécessiter moins de code répétitif, mais JNI reste largement déployé et bien compris.
Lorsque la JVM ne peut pas effectuer une action directement (communiquer avec un GPU ou un backend audio, utiliser une API spécifique à une plateforme, ou exécuter une routine critique pour les performances), un module charge une bibliothèque native et l'appelle via JNI.
Built by people who actually read the JVM internals.