¿Usar JNI en lugar de JNA para llamar al código nativo?

115

JNA parece bastante más fácil de usar para llamar a código nativo en comparación con JNI. ¿En qué casos usaría JNI sobre JNA?

Marcus Leon
fuente
Un factor importante que elegimos JNA sobre JNI, es que JNA no requirió ninguna modificación a nuestras bibliotecas nativas. Tener que personalizar las bibliotecas nativas solo para poder trabajar con Java fue un gran NO para nosotros.
kvr

Respuestas:

126
  1. JNA no admite el mapeo de clases de c ++, por lo que si está utilizando la biblioteca de c ++, necesitará un contenedor jni
  2. Si necesita mucha memoria para copiar. Por ejemplo, llama a un método que le devuelve un búfer de bytes grande, cambia algo en él, luego necesita llamar a otro método que usa este búfer de bytes. Esto requeriría que copie este búfer de c a java, luego vuelva a copiarlo de java a c. En este caso, jni ganará en rendimiento porque puede mantener y modificar este búfer en c, sin copiar.

Estos son los problemas que he encontrado. Quizás haya más. Pero, en general, el rendimiento no es tan diferente entre jna y jni, por lo que siempre que pueda usar JNA, utilícelo.

EDITAR

Esta respuesta parece ser bastante popular. Así que aquí hay algunas adiciones:

  1. Si necesita mapear C ++ o COM, hay una biblioteca de Oliver Chafic, creador de JNAerator, llamada BridJ . Todavía es una biblioteca joven, pero tiene muchas características interesantes:
    • Interoperabilidad dinámica C / C ++ / COM: llame a métodos C ++, cree objetos C ++ (¡y subclase clases C ++ desde Java!)
    • Asignaciones de tipos sencillas con un buen uso de genéricos (incluido un modelo mucho más agradable para Pointers)
    • Soporte completo de JNAerator
    • funciona en Windows, Linux, MacOS X, Solaris, Android
  2. En cuanto a la copia de memoria, creo que JNA admite ByteBuffers directos, por lo que se puede evitar la copia de memoria.

Entonces, sigo creyendo que siempre que sea posible, es mejor usar JNA o BridJ, y volver a jni si el rendimiento es crítico, porque si necesita llamar a funciones nativas con frecuencia, el impacto en el rendimiento es notable.

Denis Tulskiy
fuente
6
No estoy de acuerdo, JNA tiene muchos gastos generales. Aunque vale la pena usar su conveniencia en código que no es crítico en el tiempo
Gregory Pakosz
3
Aconsejaría precaución antes de usar BridJ para un proyecto de Android, para citar directamente de su página de descarga : "BridJ funciona parcialmente en emuladores de Android / arm (con el SDK), y probablemente incluso en dispositivos reales (no probados) " .
kizzx2
1
@StockB: si tiene una biblioteca con api donde tiene que pasar objetos C ++, o llamar a métodos en un objeto C ++, JNA no puede hacer eso. Solo puede llamar a métodos vainilla C.
Denis Tulskiy
2
Por lo que tengo entendido, JNI solo puede llamar a JNIEXPORTfunciones globales . Actualmente estoy explorando JavaCpp como una opción, que usa JNI, pero no creo que Vanilla JNI admita esto. ¿Hay alguna forma de llamar a las funciones miembro de C ++ usando Vanilla JNI que estoy pasando por alto?
StockB
1
Eche un vistazo aquí stackoverflow.com/questions/20332472/catch-a-double-hotkey
ア レ ッ ク ス
29

Es difícil responder a una pregunta tan genérica. Supongo que la diferencia más obvia es que con JNI, la conversión de tipos se implementa en el lado nativo del borde Java / nativo, mientras que con JNA, la conversión de tipos se implementa en Java. Si ya se siente bastante cómodo con la programación en C y tiene que implementar algún código nativo usted mismo, supongo que JNI no parecerá demasiado complejo. Si usted es un programador de Java y solo necesita invocar una biblioteca nativa de terceros, usar JNA es probablemente la ruta más fácil para evitar los problemas quizás no tan obvios con JNI.

Aunque nunca he comparado ninguna diferencia, lo haría debido al diseño, al menos supongo que la conversión de tipos con JNA en algunas situaciones funcionará peor que con JNI. Por ejemplo, al pasar matrices, JNA las convertirá de Java a nativas al comienzo de cada llamada a la función y de regreso al final de la llamada a la función. Con JNI, puede controlarse a sí mismo cuando se genera una "vista" nativa de la matriz, potencialmente creando solo una vista de una parte de la matriz, mantener la vista en varias llamadas de función y al final liberar la vista y decidir si desea para mantener los cambios (posiblemente requiriendo volver a copiar los datos) o descartar los cambios (no se requiere copia). Sé que puede usar una matriz nativa en las llamadas a funciones con JNA usando la clase Memory, pero esto también requerirá copia de memoria, que puede ser innecesario con JNI. Es posible que la diferencia no sea relevante, pero si su objetivo original es aumentar el rendimiento de la aplicación implementando partes de ella en código nativo, el uso de una tecnología de puente de peor rendimiento parece no ser la opción más obvia.

jarnbjo
fuente
8
  1. Está escribiendo código hace unos años antes de que existiera JNA o está apuntando a un JRE anterior a 1.4.
  2. El código con el que está trabajando no está en un archivo DLL \ SO.
  3. Está trabajando en un código incompatible con LGPL.

Eso es solo lo que se me ocurre, aunque no soy un gran usuario de ninguno de los dos. También parece que podría evitar JNA si quisiera una interfaz mejor que la que proporcionan, pero podría codificar eso en Java.

piedra metal
fuente
9
No estoy de acuerdo con 2: convertir una biblioteca estática en una biblioteca dinámica es fácil. Vea mi pregunta sobre stackoverflow.com/questions/845183/…
jb.
3
A partir de JNA 4.0, JNA tiene doble licencia bajo LGPL 2.1 y Apache License 2.0, y la que elija depende de usted
sbarber2
8

Por cierto, en uno de nuestros proyectos, mantuvimos una huella JNI muy pequeña. Usamos búferes de protocolo para representar nuestros objetos de dominio y, por lo tanto, solo teníamos una función nativa para unir Java y C (luego, por supuesto, esa función C llamaría a un montón de otras funciones).

Ustaman Sangat
fuente
5
Entonces, en lugar de una llamada a un método, tenemos el paso de mensajes. He invertido bastante tiempo en JNI y JNA y también en BridJ, pero muy pronto, todos dan un poco de miedo.
Ustaman Sangat
5

No es una respuesta directa y no tengo experiencia con JNA pero, cuando miro los Proyectos que usan JNA y veo nombres como SVNKit, IntelliJ IDEA, NetBeans IDE, etc., tiendo a creer que es una biblioteca bastante decente.

En realidad, definitivamente creo que habría usado JNA en lugar de JNI cuando tuve que hacerlo, ya que de hecho parece más simple que JNI (que tiene un proceso de desarrollo aburrido). Lástima, JNA no fue lanzado en este momento.

Pascal Thivent
fuente
3

Si desea el rendimiento de JNI pero su complejidad lo intimida, puede considerar el uso de herramientas que generen enlaces JNI automáticamente. Por ejemplo, JANET (descargo de responsabilidad: lo escribí) le permite mezclar código Java y C ++ en un solo archivo fuente y, por ejemplo, realizar llamadas de C ++ a Java utilizando la sintaxis estándar de Java. Por ejemplo, así es como imprimiría una cadena C en la salida estándar de Java:

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

JANET luego traduce el Java incrustado con comillas invertidas en las llamadas JNI apropiadas.

Dawid Kurzyniec
fuente
3

De hecho, hice algunos puntos de referencia simples con JNI y JNA.

Como ya han señalado otros, JNA es por conveniencia. No es necesario compilar o escribir código nativo cuando se usa JNA. El cargador de biblioteca nativo de JNA también es uno de los mejores y más fáciles de usar que he visto. Lamentablemente, parece que no puedes usarlo para JNI. (Es por eso que escribí una alternativa para System.loadLibrary () que usa la convención de ruta de JNA y admite la carga sin problemas desde la ruta de clase (es decir, jars)).

Sin embargo, el rendimiento de JNA puede ser mucho peor que el de JNI. Hice una prueba muy simple que llamó a una función de incremento de enteros nativa simple "return arg + 1;". Los puntos de referencia realizados con jmh mostraron que las llamadas JNI a esa función son 15 veces más rápidas que JNA.

Un ejemplo más "complejo" en el que la función nativa suma una matriz entera de 4 valores aún mostró que el rendimiento de JNI es 3 veces más rápido que JNA. La ventaja reducida probablemente se debió a la forma en que accede a las matrices en JNI: mi ejemplo creó algunas cosas y las lanzó nuevamente durante cada operación de suma.

El código y los resultados de las pruebas se pueden encontrar en github .

usuario1050755
fuente
2

Investigué JNI y JNA para comparar el rendimiento porque necesitábamos decidir que uno de ellos llamara a un dll en el proyecto y teníamos una restricción de tiempo real. Los resultados han demostrado que JNI tiene un rendimiento mayor que JNA (aproximadamente 40 veces). Tal vez haya un truco para un mejor rendimiento en JNA, pero es muy lento para un ejemplo simple.

Mustafa Kemal
fuente
1

A menos que me falte algo, ¿no es la principal diferencia entre JNA vs JNI que con JNA no se puede llamar al código Java desde el código nativo (C)?

Augusto
fuente
6
Usted puede. Con una clase de devolución de llamada que corresponde a un puntero de función en el lado C. void register_callback (void (*) (const int)); se mapearía a register_callback vacío nativo estático público (MyCallback arg1); donde MyCallback es una interfaz que extiende com.sun.jna.Callback con un solo método void apply (valor int);
Ustaman Sangat
@Augusto, esto también puede ser un comentario, por favor
swiftBoy
0

En mi aplicación específica, JNI resultó mucho más fácil de usar. Necesitaba leer y escribir transmisiones continuas desde y hacia un puerto serie, y nada más. En lugar de intentar aprender la infraestructura muy involucrada en JNA, me resultó mucho más fácil crear un prototipo de la interfaz nativa en Windows con una DLL de propósito especial que exportaba solo seis funciones:

  1. DllMain (necesario para interactuar con Windows)
  2. OnLoad (solo hace un OutputDebugString para que pueda saber cuándo se adjunta el código Java)
  3. OnUnload (ídem)
  4. Abrir (abre el puerto, comienza a leer y escribir hilos)
  5. QueueMessage (pone en cola los datos para la salida del hilo de escritura)
  6. GetMessage (espera y devuelve los datos recibidos por el hilo leído desde la última llamada)
Walter Oney
fuente