Learn · concepts
Cos'è JNI? Interfaccia Nativa Java per Modder
JNI (Java Native Interface) è il ponte integrato di Java per chiamare C/C++ dalla JVM e viceversa. È il metodo standard con cui un mod di Minecraft carica una libreria nativa.
Cos'è JNI in termini semplici?
JNI (Java Native Interface) è la parte di Java che collega due mondi: il bytecode gestito sulla JVM e il codice macchina compilato scritto in C o C++. Un metodo riceve la parola chiave native e non ha un corpo, e a runtime la JVM lo collega a una funzione corrispondente in una libreria nativa caricata. Da lì, entrambe le parti scambiano argomenti e valori di ritorno.
Il confine non è unidirezionale. La funzione nativa riceve un puntatore JNIEnv, e tale handle è la porta di accesso a Java: espone chiamate per la lettura e la scrittura di campi Java, l'invocazione di metodi Java, la costruzione di oggetti e la generazione di eccezioni. Quindi lo stesso ponte che permette a Java di chiamare verso l'esterno permette anche a C di chiamare verso l'interno.
Perché un mod o un client di Minecraft dovrebbe interagire con il codice nativo?
La maggior parte del codice Java non ha bisogno di JNI. Un mod lo utilizza quando la JVM da sola non può svolgere il compito, di solito per la grafica, l'audio, l'integrazione con il sistema operativo o una routine che deve essere veloce. La libreria nativa svolge il lavoro pesante e JNI è il modo standard per caricarla e richiamarla.
Riutilizzare una libreria C collaudata
Un codice base nativo e consolidato già risolve il problema. Riscriverlo in Java sarebbe uno sforzo inutile, quindi la mod si lega ad esso.
Raggiungi il sistema operativo
Alcune funzionalità della piattaforma non dispongono di un'API Java pura. Una chiamata nativa diventa l'unico percorso per accedervi.
Percorso infuocato
Una routine numerica stretta o di basso livello può essere eseguita più velocemente come codice nativo compilato rispetto al bytecode sulla JVM.
Gestire hardware e driver
Il rendering, l'audio e l'accesso ai dispositivi si trovano spesso dietro le librerie native che la JNI espone al lato Java.
Come funziona una chiamata JNI end-to-end?
Il flusso è identico su ogni piattaforma: si dichiara il metodo in Java, si compila una funzione C corrispondente in una libreria condivisa, la si carica a runtime, si lascia che la JVM le colleghi per nome, quindi si effettua la chiamata. I nomi delle funzioni seguono uno schema fisso in modo che la JVM possa risolverli, e un header generato specifica la firma esatta da implementare.
| Fase | Cosa succede |
|---|---|
| Dichiarazione | Un metodo Java viene scritto con la parola chiave native e lasciato vuoto sul lato Java. |
| Build | Una funzione C o C++ viene compilata in una libreria condivisa: .dll su Windows, .so su Linux, .dylib su macOS. |
| Caricamento | Java chiama System.loadLibrary per caricare la libreria durante l'esecuzione del programma. |
| Collegamento | La JVM associa ogni metodo native alla sua funzione C tramite una firma "name-mangled". |
| Chiamata | Java invoca il metodo; il controllo passa al codice nativo e ritorna con un risultato. |
Quanto costa a te JNI?
JNI è potente e presenta spigoli vivi. Poiché il codice nativo viene eseguito al di fuori della rete di sicurezza della JVM, un errore lì fa crashare l'intero processo anziché generare un'eccezione gestibile. La memoria allocata dal lato nativo è invisibile al garbage collector, quindi le perdite di memoria si accumulano a meno che non vengano liberate manualmente. Ogni attraversamento comporta un overhead, il che rende costose le chiamate oltre il confine all'interno di un ciclo stretto. E le build native sono bloccate alla piattaforma: una .dll di Windows non si carica su Linux senza una compilazione separata.
Cosa compra
- Accesso diretto a librerie C e C++ esistenti
- Un percorso verso le funzionalità del sistema operativo e dell'hardware che la JVM non può raggiungere
- Esecuzione a velocità nativa per le routine più utilizzate
Costo
- Un crash nativo può mandare in crash l'intera JVM
- Gestione manuale della memoria senza un sistema di garbage collection di supporto
- Build specifiche per piattaforma per la manutenzione e la distribuzione
- Overhead che attraversa i confini nei loop critici
JNI rispetto alla Foreign Function and Memory API
JNI è il meccanismo originale, ma non è più l'unico. Le versioni più recenti di Java includono la Foreign Function and Memory (FFM) API, progettata per chiamare codice nativo con meno codice ripetitivo e garanzie di sicurezza più rigorose. JNI continua a dominare gli strumenti esistenti perché è presente ovunque e ben compreso.
| JNI | FFM API | |
|---|---|---|
| Età | Originale, presente fin dalle prime versioni di Java | Moderna, aggiunta nelle ultime release |
| Codice ripetitivo | Header generati, funzioni C con nomi modificati | Handle dei metodi definiti in Java, nessuna funzione C di supporto |
| Sicurezza della memoria | Manuale; errori possono corrompere la JVM | Segmenti delimitati con controlli più severi |
| Adozione | Diffusa tra le librerie esistenti | In crescita, per i nuovi codebase |
DOMANDE FREQUENTI
È integrato. JNI viene fornito con il JDK e la JVM, quindi il lato Java non necessita di nulla di aggiuntivo. La compilazione della parte nativa richiede comunque una toolchain C o C++ per ogni piattaforma di destinazione.
No. La metà Java rimane portabile, ma una libreria nativa viene compilata per un singolo sistema operativo e CPU. La distribuzione multipiattaforma significa creare e includere un file .dll, .so e .dylib separato.
No. JNI è il meccanismo originale. Le versioni recenti di Java aggiungono l'API Foreign Function and Memory, che mira a essere più sicura e a richiedere meno codice ripetitivo, ma JNI rimane ampiamente diffuso e ben compreso.
Quando la JVM non può fare qualcosa direttamente (comunicare con una GPU o un backend audio, utilizzare un'API esclusiva della piattaforma o eseguire una routine ad alte prestazioni), un mod carica una libreria nativa e la invoca tramite JNI.
Built by people who actually read the JVM internals.