Usar JNI em vez de JNA para chamar o código nativo?

115

JNA parece um pouco mais fácil de usar para chamar código nativo em comparação com JNI. Em que casos você usaria JNI em vez de JNA?

Marcus leon
fonte
Um fator importante que escolhemos JNA em vez de JNI, é que JNA não exigiu nenhuma modificação em nossas bibliotecas nativas. Ter que personalizar bibliotecas nativas apenas para poder trabalhar com Java era um grande NÃO para nós.
kvr

Respostas:

126
  1. JNA não suporta mapeamento de classes c ++, portanto, se você estiver usando a biblioteca c ++, precisará de um wrapper jni
  2. Se você precisar de muita cópia de memória. Por exemplo, você chama um método que retorna um grande buffer de bytes, muda algo nele e precisa chamar outro método que usa esse buffer de bytes. Isso exigiria que você copiasse esse buffer de c para java e, em seguida, copie-o de volta de java para c. Neste caso, o jni ganhará em desempenho porque você pode manter e modificar este buffer em c, sem copiar.

Esses são os problemas que encontrei. Talvez haja mais. Mas, em geral, o desempenho não é tão diferente entre jna e jni, portanto, onde quer que você possa usar JNA, use-o.

EDITAR

Esta resposta parece ser bastante popular. Então, aqui estão algumas adições:

  1. Se você precisar mapear C ++ ou COM, existe uma biblioteca de Oliver Chafic, criador do JNAerator, chamada BridJ . Ainda é uma biblioteca jovem, mas tem muitos recursos interessantes:
    • Interoperabilidade C / C ++ / COM dinâmica: chamar métodos C ++, criar objetos C ++ (e classes C ++ de subclasse de Java!)
    • Mapeamentos de tipo simples com bom uso de genéricos (incluindo um modelo muito mais agradável para ponteiros)
    • Suporte total ao JNAerator
    • funciona em Windows, Linux, MacOS X, Solaris, Android
  2. Quanto à cópia de memória, acredito que o JNA oferece suporte a ByteBuffers diretos, portanto, a cópia de memória pode ser evitada.

Portanto, ainda acredito que, sempre que possível, é melhor usar JNA ou BridJ e reverter para jni se o desempenho for crítico, porque se você precisar chamar funções nativas com frequência, o impacto no desempenho será perceptível.

Denis Tulskiy
fonte
6
Eu discordo, JNA tem muita sobrecarga. Embora valha a pena usar sua conveniência em código não crítico de tempo
Gregory Pakosz
3
Aconselho cautela antes de usar BridJ para um projeto Android, para citar diretamente de sua página de download : "BridJ funciona parcialmente em emuladores Android / arm (com o SDK) e provavelmente até mesmo em dispositivos reais (não testados). "
kizzx2
1
@StockB: se você tem uma biblioteca com api onde você tem que passar objetos C ++, ou chamar métodos em objetos C ++, JNA não pode fazer isso. Ele só pode chamar métodos C vanilla.
Denis Tulskiy
2
Tanto quanto eu entendo, JNI só pode chamar JNIEXPORTfunções globais . Atualmente estou explorando JavaCpp como uma opção, que usa JNI, mas não acho que o vanilla JNI suporte isso. Existe uma maneira de chamar funções de membro C ++ usando vanilla JNI que estou ignorando?
StockB
1
Por favor, dê uma olhada aqui stackoverflow.com/questions/20332472/catch-a-double-hotkey
ア レ ッ ク ス
29

É difícil responder a uma pergunta tão genérica. Suponho que a diferença mais óbvia é que com JNI, a conversão de tipo é implementada no lado nativo da fronteira Java / nativa, enquanto com JNA, a conversão de tipo é implementada em Java. Se você já se sente bastante confortável com a programação em C e precisa implementar algum código nativo sozinho, eu presumiria que o JNI não parecerá muito complexo. Se você é um programador Java e só precisa invocar uma biblioteca nativa de terceiros, usar JNA é provavelmente o caminho mais fácil para evitar os problemas talvez não tão óbvios com JNI.

Embora eu nunca tenha feito o benchmark de nenhuma diferença, por causa do design, pelo menos suponha que a conversão de tipo com JNA em algumas situações terá um desempenho pior do que com JNI. Por exemplo, ao passar arrays, o JNA os converterá de Java para nativo no início de cada chamada de função e de volta no final da chamada de função. Com JNI, você pode controlar a si mesmo quando uma "visão" nativa da matriz é gerada, potencialmente criando apenas uma visão de uma parte da matriz, manter a visão em várias chamadas de função e no final liberar a visão e decidir se deseja para manter as alterações (potencialmente exigindo a cópia de volta dos dados) ou descartar as alterações (nenhuma cópia necessária). Eu sei que você pode usar uma matriz nativa em chamadas de função com JNA usando a classe Memory, mas isso também exigirá a cópia de memória, o que pode ser desnecessário com JNI. A diferença pode não ser relevante, mas se seu objetivo original é aumentar o desempenho do aplicativo implementando partes dele em código nativo, usar uma tecnologia de ponte de pior desempenho não parece ser a escolha mais óbvia.

Jarnbjo
fonte
8
  1. Você está escrevendo código alguns anos atrás, antes de haver JNA, ou está planejando um JRE anterior ao 1.4.
  2. O código com o qual você está trabalhando não está em uma DLL \ SO.
  3. Você está trabalhando em um código incompatível com LGPL.

Isso é apenas o que posso fazer de cabeça, embora eu não seja um usuário pesado de qualquer um. Também parece que você pode evitar o JNA se quiser uma interface melhor do que a que eles fornecem, mas você pode codificar em java.

metal de pedra
fonte
9
Eu discordo sobre 2 - converter lib estática em lib dinâmica é fácil. Veja minha pergunta abput it stackoverflow.com/questions/845183/…
jb.
3
A partir do JNA 4.0, o JNA tem licença dupla sob ambos LGPL 2.1 e Apache License 2.0, e a escolha é sua
sbarber2
8

A propósito, em um de nossos projetos, mantivemos uma pegada JNI muito pequena. Usamos buffers de protocolo para representar nossos objetos de domínio e, portanto, tínhamos apenas uma função nativa para fazer a ponte entre Java e C (então, é claro, essa função C chamaria várias outras funções).

Ustaman Sangat
fonte
5
Portanto, em vez de uma chamada de método, temos a passagem de mensagens. Eu investi um pouco de tempo no JNI e JNA e também no BridJ, mas logo, todos eles ficarão um pouco assustadores.
Ustaman Sangat
5

Não é uma resposta direta e não tenho experiência com JNA, mas, quando olho para Projetos usando JNA e vejo nomes como SVNKit, IntelliJ IDEA, NetBeans IDE, etc, tenho tendência a acreditar que é uma biblioteca bastante decente.

Na verdade, eu definitivamente acho que teria usado JNA em vez de JNI quando era necessário, pois parece mais simples do que JNI (que tem um processo de desenvolvimento chato). Que pena, JNA não foi lançado neste momento.

Pascal Thivent
fonte
3

Se você deseja desempenho JNI, mas está intimidado por sua complexidade, pode considerar o uso de ferramentas que geram ligações JNI automaticamente. Por exemplo, JANET (isenção de responsabilidade: eu escrevi) permite que você misture código Java e C ++ em um único arquivo de origem e, por exemplo, faça chamadas de C ++ para Java usando a sintaxe Java padrão. Por exemplo, veja como você imprimiria uma string C na saída padrão Java:

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

O JANET então converte o Java embutido no crase nas chamadas JNI apropriadas.

Dawid Kurzyniec
fonte
3

Na verdade, fiz alguns benchmarks simples com JNI e JNA.

Como outros já apontaram, JNA é por conveniência. Você não precisa compilar ou escrever código nativo ao usar JNA. O carregador de biblioteca nativa do JNA também é um dos melhores / mais fáceis de usar que já vi. Infelizmente, você não pode usá-lo para JNI ao que parece. (É por isso que escrevi uma alternativa para System.loadLibrary () que usa a convenção de caminho do JNA e oferece suporte ao carregamento direto do caminho de classe (ou seja, jars).)

O desempenho do JNA, entretanto, pode ser muito pior do que o do JNI. Fiz um teste muito simples que chamou uma função de incremento de inteiro nativo simples "return arg + 1;". Benchmarks feitos com jmh mostraram que as chamadas JNI para essa função são 15 vezes mais rápidas do que JNA.

Um exemplo mais "complexo" em que a função nativa soma um array inteiro de 4 valores ainda mostrou que o desempenho do JNI é 3 vezes mais rápido do que o do JNA. A vantagem reduzida provavelmente se deu por causa de como você acessa arrays no JNI: meu exemplo criou algumas coisas e as liberou novamente durante cada operação de soma.

O código e os resultados dos testes podem ser encontrados no github .

user1050755
fonte
2

Eu investiguei JNI e JNA para comparação de desempenho porque precisávamos decidir um deles para chamar uma dll no projeto e tínhamos uma restrição de tempo real. Os resultados mostraram que o JNI tem um desempenho superior ao do JNA (aproximadamente 40 vezes). Talvez haja um truque para melhor desempenho em JNA, mas é muito lento para um exemplo simples.

Mustafa Kemal
fonte
1

A menos que esteja faltando alguma coisa, a principal diferença entre JNA e JNI não é que com JNA você não pode chamar o código Java do código nativo (C)?

Augusto
fonte
6
Você pode. Com uma classe de retorno de chamada que corresponde a um ponteiro de função no lado C. void register_callback (void (*) (const int)); seria mapeado para público estático nativo void register_callback (MyCallback arg1); onde MyCallback é uma interface que estende com.sun.jna.Callback com um único método void apply (int value);
Ustaman Sangat
@Augusto, este também pode ser um comentário, por favor
swiftBoy
0

Em meu aplicativo específico, o JNI provou ser muito mais fácil de usar. Eu precisava ler e gravar fluxos contínuos de e para uma porta serial - e nada mais. Em vez de tentar aprender a infraestrutura muito envolvida em JNA, achei muito mais fácil criar um protótipo da interface nativa do Windows com uma DLL de propósito especial que exportava apenas seis funções:

  1. DllMain (necessário para fazer interface com o Windows)
  2. OnLoad (apenas faz um OutputDebugString para que eu possa saber quando o código Java é anexado)
  3. OnUnload (idem)
  4. Abrir (abre a porta, inicia a leitura e gravação de threads)
  5. QueueMessage (enfileira dados para saída pelo thread de gravação)
  6. GetMessage (espera e retorna dados recebidos pelo thread de leitura desde a última chamada)
Walter Oney
fonte