JNA は、JNI に比べて、ネイティブ コードを呼び出すのにかなり使いやすいようです。JNA よりも JNI を使用するのはどのような場合ですか?
10 に答える
- JNA は C++ クラスのマッピングをサポートしていないため、C++ ライブラリを使用している場合は、jni ラッパーが必要になります。
- 大量のメモリコピーが必要な場合。たとえば、大きなバイト バッファを返す 1 つのメソッドを呼び出し、その中の何かを変更すると、このバイト バッファを使用する別のメソッドを呼び出す必要があります。これには、このバッファを c から Java にコピーしてから、Java から c にコピーし直す必要があります。この場合、このバッファーをコピーせずに c で保持および変更できるため、jni がパフォーマンスで勝ちます。
これらは私が遭遇した問題です。多分もっとある。しかし、一般的にパフォーマンスは jna と jni でそれほど変わらないため、JNA を使用できる場合は、JNA を使用してください。
編集
この答えはかなり人気があるようです。したがって、ここにいくつかの追加があります:
- C++ または COM をマッピングする必要がある場合は、JNAerator の作成者である Oliver Chafic による BridJ というライブラリがあります。まだ新しいライブラリですが、多くの興味深い機能があります。
- 動的な C / C++ / COM 相互運用性 : C++ メソッドを呼び出し、C++ オブジェクトを作成します (そして Java から C++ クラスをサブクラス化します!)
- ジェネリックをうまく使用した単純な型マッピング (ポインターのより優れたモデルを含む)
- JNAerator の完全なサポート
- Windows、Linux、MacOS X、Solaris、Android で動作
- メモリ コピーに関しては、JNA が直接 ByteBuffers をサポートしているため、メモリ コピーを回避できると思います。
したがって、可能な限り JNA または BridJ を使用し、パフォーマンスが重要な場合は jni に戻すことをお勧めします。ネイティブ関数を頻繁に呼び出す必要がある場合、パフォーマンスへの影響が顕著になるからです。
このような一般的な質問に答えることは困難です。最も明白な違いは、JNI では型変換が Java/ネイティブ境界のネイティブ側に実装されているのに対し、JNA では型変換が Java で実装されていることだと思います。C でのプログラミングに慣れていて、ネイティブ コードを自分で実装する必要がある場合は、JNI はそれほど複雑ではないと思います。あなたが Java プログラマーで、サード パーティのネイティブ ライブラリを呼び出すだけでよい場合、JNA を使用することが、JNI に関するそれほど明白ではない問題を回避するための最も簡単な方法です。
私は違いをベンチマークしたことはありませんが、設計上の理由から、JNA を使用した型変換は、状況によっては JNI を使用した場合よりもパフォーマンスが低下すると少なくとも想定しています。たとえば、配列を渡す場合、JNA は、各関数呼び出しの開始時にこれらを Java からネイティブに変換し、関数呼び出しの最後に戻します。JNI を使用すると、配列のネイティブ「ビュー」が生成されたときに自分で制御できます。潜在的に配列の一部のビューのみを作成し、複数の関数呼び出しにわたってビューを保持し、最後にビューを解放して、必要かどうかを決定できます。変更を保持するか (データをコピーして戻す必要がある場合があります)、変更を破棄します (コピーは不要です)。Memory クラスを使用して JNA で関数呼び出し全体でネイティブ配列を使用できることは知っていますが、これにはメモリのコピーも必要になります。これは JNI では不要な場合があります。違いは関係ないかもしれませんが、本来の目的がアプリケーションの一部をネイティブ コードに実装することでアプリケーションのパフォーマンスを向上させることである場合、パフォーマンスの低いブリッジ テクノロジを使用することは、最も明白な選択肢ではないようです。
ところで、私たちのプロジェクトの 1 つで、JNI フットプリントを非常に小さくしました。ドメイン オブジェクトを表すためにプロトコル バッファを使用したため、Java と C を橋渡しするためのネイティブ関数は 1 つしかありませんでした (もちろん、その C 関数は他の多くの関数を呼び出します)。
- JNAが登場する数年前にコードを書いているか、1.4より前のJREをターゲットにしています。
- 使用しているコードは DLL\SO にありません。
- LGPL と互換性のないコードに取り組んでいます。
私はどちらもヘビーユーザーではありませんが、それは私が頭のてっぺんから思いつくことができるものです。JNAが提供するものよりも優れたインターフェースが必要な場合は、JNAを避けるかもしれないようですが、Javaでそれをコーディングすることもできます。
直接的な答えではなく、私は JNA の経験がありませんが、JNAを使用したプロジェクトを見て、 SVNKit、IntelliJ IDEA、NetBeans IDE などの名前を見ると、かなりまともなライブラリだと思う傾向があります。
実際、JNI (退屈な開発プロセスを持つ) よりも実際に単純に見えるので、JNI の代わりに JNA を使用しなければならなかったと思います。残念ながら、現時点では JNA はリリースされていません。
実際に、JNI と JNA を使用していくつかの簡単なベンチマークを行いました。
他の人がすでに指摘しているように、JNA は便宜上のものです。JNA を使用する場合、ネイティブ コードをコンパイルまたは記述する必要はありません。JNA のネイティブ ライブラリ ローダーも、私が今まで見た中で最も使いやすく、使いやすいものの 1 つです。残念ながら、JNI には使用できないようです。(そのため、JNA のパス規則を使用し、クラスパス (つまり jar) からのシームレスなロードをサポートするSystem.loadLibrary() の代替案を作成しました。)
ただし、JNA のパフォーマンスは、JNI のパフォーマンスよりもはるかに悪い場合があります。単純なネイティブ整数インクリメント関数「return arg + 1;」を呼び出す非常に単純なテストを作成しました。jmh で行われたベンチマークは、その関数への JNI 呼び出しが JNA よりも 15 倍高速であることを示しました。
ネイティブ関数が 4 つの値の整数配列を合計する、より「複雑な」例でも、JNI のパフォーマンスは JNA よりも 3 倍速いことが示されました。利点が減少したのは、おそらく JNI で配列にアクセスする方法が原因でした。私の例では、いくつかのものを作成し、各合計操作中に再び解放しました。
コードとテスト結果はgithubにあります。
JNI のパフォーマンスは必要だが、その複雑さが気になる場合は、JNI バインディングを自動的に生成するツールの使用を検討してください。たとえば、JANET (免責事項: 私が書きました) を使用すると、Java と C++ のコードを 1 つのソース ファイルに混在させることができます。たとえば、標準の Java 構文を使用して C++ から Java への呼び出しを行うことができます。たとえば、C 文字列を Java 標準出力に出力する方法は次のとおりです。
native "C++" void printHello() {
const char* helloWorld = "Hello, World!";
`System.out.println(#$(helloWorld));`
}
次に、JANET は、バックティックが埋め込まれた Java を適切な JNI 呼び出しに変換します。
JNI と JNA を調査してパフォーマンスを比較しました。これは、プロジェクトで dll を呼び出すためにどちらかを決定する必要があり、リアルタイムの制約があったためです。その結果、JNI は JNA よりも優れたパフォーマンス (約 40 倍) を持っていることがわかりました。JNA のパフォーマンスを向上させるためのトリックがあるかもしれませんが、単純な例では非常に遅いです。
私が何かを見逃していない限り、JNA と JNI の主な違いは、JNA ではネイティブ (C) コードから Java コードを呼び出すことができないということではないでしょうか?