Utilizzare JNI invece di JNA per chiamare il codice nativo?


115

JNA sembra un po 'più facile da usare per chiamare il codice nativo rispetto a JNI. In quali casi useresti JNI su JNA?


Un fattore importante per cui abbiamo scelto JNA rispetto a JNI, è che JNA non ha richiesto alcuna modifica alle nostre librerie native. Dover personalizzare le librerie native solo per poter lavorare con Java è stato un grande NO per noi.
kvr

Risposte:


126
  1. JNA non supporta la mappatura delle classi c ++, quindi se stai usando la libreria c ++ avrai bisogno di un wrapper jni
  2. Se hai bisogno di molta memoria per copiare. Ad esempio, si chiama un metodo che restituisce un buffer di byte di grandi dimensioni, si modifica qualcosa in esso, quindi è necessario chiamare un altro metodo che utilizza questo buffer di byte. Ciò richiederebbe di copiare questo buffer da c a java, quindi copiarlo di nuovo da java a c. In questo caso jni vincerà in termini di prestazioni perché puoi mantenere e modificare questo buffer in c, senza copiare.

Questi sono i problemi che ho riscontrato. Forse c'è di più. Ma in generale le prestazioni non sono così diverse tra jna e jni, quindi ovunque tu possa usare JNA, usalo.

MODIFICARE

Questa risposta sembra essere abbastanza popolare. Quindi ecco alcune aggiunte:

  1. Se hai bisogno di mappare C ++ o COM, c'è una libreria di Oliver Chafic, creatore di JNAerator, chiamata BridJ . È ancora una biblioteca giovane, ma ha molte caratteristiche interessanti:
    • Interoperabilità dinamica C / C ++ / COM: chiama metodi C ++, crea oggetti C ++ (e classi C ++ sottoclasse da Java!)
    • Mappature dei tipi semplici con un buon uso dei generici (incluso un modello molto più carino per i puntatori)
    • Supporto completo JNAerator
    • funziona su Windows, Linux, MacOS X, Solaris, Android
  2. Per quanto riguarda la copia della memoria, credo che JNA supporti ByteBuffer diretti, quindi la copia della memoria può essere evitata.

Quindi, continuo a credere che, ove possibile, sia meglio usare JNA o BridJ e tornare a jni se le prestazioni sono critiche, perché se è necessario chiamare frequentemente le funzioni native, il calo delle prestazioni è evidente.


6
Non sono d'accordo, JNA ha molte spese generali. Anche se vale la pena utilizzarne la convenienza in un codice non time-critical
Gregory Pakosz

3
Consiglierei cautela prima di utilizzare BridJ per un progetto Android, per citare direttamente dalla sua pagina di download : "BridJ funziona parzialmente su emulatori Android / arm (con l'SDK) e probabilmente anche su dispositivi reali (non testati). "
kizzx2

1
@StockB: se hai una libreria con API in cui devi passare oggetti C ++, o chiamare metodi su oggetti C ++, JNA non può farlo. Può chiamare solo metodi C vanilla.
Denis Tulskiy

2
Per quanto ho capito, JNI può chiamare solo JNIEXPORTfunzioni globali . Attualmente sto esplorando JavaCpp come opzione, che utilizza JNI, ma non credo che JNI vanilla lo supporti. C'è un modo per chiamare le funzioni membro C ++ usando JNI vaniglia che sto trascurando?
StockB


29

È difficile rispondere a una domanda così generica. Suppongo che la differenza più evidente sia che con JNI la conversione del tipo è implementata sul lato nativo del bordo Java / nativo, mentre con JNA la conversione del tipo è implementata in Java. Se ti senti già abbastanza a tuo agio con la programmazione in C e devi implementare del codice nativo da solo, presumo che JNI non sembrerà troppo complesso. Se sei un programmatore Java e hai solo bisogno di richiamare una libreria nativa di terze parti, l'uso di JNA è probabilmente il percorso più semplice per evitare i problemi forse non così ovvi con JNI.

Sebbene non abbia mai valutato le differenze, lo farei a causa del design, almeno supponiamo che la conversione del tipo con JNA in alcune situazioni funzionerà peggio che con JNI. Ad esempio, quando si passano gli array, JNA li convertirà da Java a nativi all'inizio di ogni chiamata di funzione e di nuovo alla fine della chiamata di funzione. Con JNI, puoi controllarti quando viene generata una "vista" nativa dell'array, creando potenzialmente solo una vista di una parte dell'array, mantenere la vista su più chiamate di funzione e alla fine rilasciare la vista e decidere se vuoi per conservare le modifiche (potenzialmente richiedendo di copiare i dati indietro) o scartare le modifiche (nessuna copia richiesta). So che puoi utilizzare un array nativo tra le chiamate di funzione con JNA utilizzando la classe Memory, ma ciò richiederà anche la copia della memoria, che potrebbe non essere necessario con JNI. La differenza potrebbe non essere rilevante, ma se l'obiettivo originale è aumentare le prestazioni dell'applicazione implementando parti di esso nel codice nativo, l'utilizzo di una tecnologia bridge con prestazioni peggiori non sembra essere la scelta più ovvia.


8
  1. Stai scrivendo codice alcuni anni fa prima che ci fosse JNA o stai prendendo di mira un JRE pre 1.4.
  2. Il codice con cui stai lavorando non è in una DLL \ SO.
  3. Stai lavorando su un codice incompatibile con LGPL.

Questo è solo ciò che riesco a tirar fuori dalla mia testa, anche se non ne sono un utente pesante. Sembra anche che potresti evitare JNA se volessi un'interfaccia migliore di quella che forniscono, ma potresti codificarla in java.


9
Non sono d'accordo su 2 - convertire la lib statica in una lib dinamica è facile. Vedere la mia domanda è abput stackoverflow.com/questions/845183/...~~V~~singular~~3rd
jb.

3
A partire da JNA 4.0, JNA ha una doppia licenza sia con LGPL 2.1 che con licenza Apache 2.0, e la scelta dipende da te
sbarber2

8

A proposito, in uno dei nostri progetti, abbiamo mantenuto un'impronta JNI molto piccola. Abbiamo usato buffer di protocollo per rappresentare i nostri oggetti di dominio e quindi avevamo solo una funzione nativa per collegare Java e C (quindi ovviamente quella funzione C avrebbe chiamato un mucchio di altre funzioni).


5
Quindi, invece di una chiamata al metodo, abbiamo il passaggio di messaggi. Ho investito un bel po 'di tempo in JNI e JNA e anche in BridJ, ma ben presto diventano tutti un po' troppo spaventosi.
Ustaman Sangat

5

Non è una risposta diretta e non ho esperienza con JNA, ma quando guardo i progetti che utilizzano JNA e vedo nomi come SVNKit, IntelliJ IDEA, NetBeans IDE, ecc., Tendo a credere che sia una libreria abbastanza decente.

In realtà, penso sicuramente che avrei usato JNA invece di JNI quando dovevo perché sembra davvero più semplice di JNI (che ha un processo di sviluppo noioso). Peccato, JNA non è stato rilasciato in questo momento.


3

Se desideri prestazioni JNI ma sei scoraggiato dalla sua complessità, potresti prendere in considerazione l'utilizzo di strumenti che generano automaticamente collegamenti JNI. Ad esempio, JANET (disclaimer: l'ho scritto io) consente di combinare codice Java e C ++ in un unico file sorgente, e ad esempio effettuare chiamate da C ++ a Java utilizzando la sintassi Java standard. Ad esempio, ecco come stampare una stringa C sull'output standard Java:

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

JANET quindi traduce il Java incorporato nel backtick nelle chiamate JNI appropriate.


3

In realtà ho fatto alcuni semplici benchmark con JNI e JNA.

Come altri hanno già sottolineato, JNA è per comodità. Non è necessario compilare o scrivere codice nativo quando si utilizza JNA. Il caricatore di librerie nativo di JNA è anche uno dei migliori / più facili da usare che abbia mai visto. Purtroppo, a quanto pare non puoi usarlo per JNI. (Ecco perché ho scritto un'alternativa per System.loadLibrary () che utilizza la convenzione del percorso di JNA e supporta il caricamento senza interruzioni dal classpath (cioè i jar).)

Le prestazioni di JNA, tuttavia, possono essere molto peggiori di quelle di JNI. Ho fatto un test molto semplice che ha chiamato una semplice funzione di incremento intero nativo "return arg + 1;". I benchmark eseguiti con jmh hanno mostrato che le chiamate JNI a quella funzione sono 15 volte più veloci di JNA.

Un esempio più "complesso" in cui la funzione nativa riassume un array intero di 4 valori mostrava ancora che le prestazioni JNI sono 3 volte più veloci di JNA. Il vantaggio ridotto era probabilmente dovuto al modo in cui accedi agli array in JNI: il mio esempio ha creato alcune cose e le ha rilasciate di nuovo durante ogni operazione di somma.

Codice e risultati dei test possono essere trovati su github .


2

Ho studiato JNI e JNA per il confronto delle prestazioni perché dovevamo decidere che uno di loro chiamasse una dll nel progetto e avevamo un vincolo di tempo reale. I risultati hanno mostrato che JNI ha prestazioni maggiori di JNA (circa 40 volte). Forse c'è un trucco per migliorare le prestazioni in JNA, ma è molto lento per un semplice esempio.


1

A meno che non mi manchi qualcosa, non è la differenza principale tra JNA vs JNI che con JNA non puoi chiamare il codice Java dal codice nativo (C)?


6
Puoi. Con una classe di callback che corrisponde a un puntatore a funzione sul lato C. void register_callback (void (*) (const int)); sarebbe mappato a pubblico statico nativo void register_callback (MyCallback arg1); dove MyCallback è un'interfaccia che estende com.sun.jna.Callback con un unico metodo void apply (valore int);
Ustaman Sangat

@Augusto questo può essere anche un commento per favore
swiftBoy

0

Nella mia specifica applicazione, JNI si è rivelato molto più facile da usare. Avevo bisogno di leggere e scrivere flussi continui da e verso una porta seriale e nient'altro. Piuttosto che provare a imparare l'infrastruttura molto coinvolta in JNA, ho trovato molto più facile prototipare l'interfaccia nativa in Windows con una DLL speciale che esportava solo sei funzioni:

  1. DllMain (richiesto per interfacciarsi con Windows)
  2. OnLoad (fa solo un OutputDebugString in modo da poter sapere quando il codice Java si collega)
  3. OnUnload (idem)
  4. Apri (apre la porta, avvia la lettura e la scrittura dei thread)
  5. QueueMessage (accoda i dati per l'output dal thread di scrittura)
  6. GetMessage (attende e restituisce i dati ricevuti dal thread letto dall'ultima chiamata)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.