Gunakan JNI, bukan JNA untuk memanggil kode asli?

115

JNA tampaknya sedikit lebih mudah digunakan untuk memanggil kode asli dibandingkan dengan JNI. Dalam kasus apa Anda akan menggunakan JNI melalui JNA?

Marcus Leon
sumber
Satu faktor penting bahwa kami memilih JNA daripada JNI, adalah bahwa JNA tidak memerlukan modifikasi apa pun pada pustaka asli kami. Harus menyesuaikan pustaka asli hanya untuk dapat bekerja dengan Java adalah TIDAK besar bagi kami.
kvr

Jawaban:

126
  1. JNA tidak mendukung pemetaan kelas c ++, jadi jika Anda menggunakan pustaka c ++, Anda memerlukan pembungkus jni
  2. Jika Anda membutuhkan banyak penyalinan memori. Misalnya, Anda memanggil satu metode yang mengembalikan buffer byte besar, Anda mengubah sesuatu di dalamnya, lalu Anda perlu memanggil metode lain yang menggunakan buffer byte ini. Ini akan meminta Anda untuk menyalin buffer ini dari c ke java, lalu menyalinnya kembali dari java ke c. Dalam hal ini jni akan menang dalam performa karena Anda dapat menyimpan dan memodifikasi buffer ini di c, tanpa menyalin.

Ini adalah masalah yang saya temui. Mungkin masih ada lagi. Tetapi secara umum kinerjanya tidak jauh berbeda antara jna dan jni, jadi di mana pun Anda dapat menggunakan JNA, gunakanlah.

EDIT

Jawaban ini sepertinya cukup populer. Berikut beberapa tambahannya:

  1. Jika Anda perlu memetakan C ++ atau COM, ada perpustakaan oleh Oliver Chafic, pencipta JNAerator, yang disebut BridJ . Ini masih merupakan perpustakaan muda, tetapi memiliki banyak fitur menarik:
    • Dynamic C / C ++ / COM interop: panggil metode C ++, buat objek C ++ (dan subclass C ++ class dari Java!)
    • Jenis pemetaan langsung dengan penggunaan obat generik yang baik (termasuk model yang jauh lebih baik untuk Pointer)
    • Dukungan penuh JNAerator
    • bekerja di Windows, Linux, MacOS X, Solaris, Android
  2. Mengenai penyalinan memori, saya yakin JNA mendukung ByteBuffers langsung, sehingga penyalinan memori dapat dihindari.

Jadi, saya masih percaya bahwa jika memungkinkan, lebih baik menggunakan JNA atau BridJ, dan kembali ke jni jika kinerja sangat penting, karena jika Anda perlu sering memanggil fungsi asli, kinerja yang dicapai akan terlihat.

Denis Tulskiy
sumber
6
Saya tidak setuju, JNA memiliki banyak biaya tambahan. Meskipun kemudahannya layak digunakan dalam kode kritis bukan waktu
Gregory Pakosz
3
Saya menyarankan agar berhati-hati sebelum menggunakan BridJ untuk proyek Android, mengutip langsung dari halaman unduhannya: "BridJ berfungsi sebagian pada emulator Android / arm (dengan SDK), dan bahkan mungkin pada perangkat sebenarnya (belum teruji). "
kizzx2
1
@StockB: jika Anda memiliki perpustakaan dengan api di mana Anda harus meneruskan objek C ++, atau memanggil metode pada objek C ++, JNA tidak dapat melakukannya. Itu hanya dapat memanggil metode vanilla C.
Denis Tulskiy
2
Sejauh yang saya mengerti, JNI hanya dapat memanggil JNIEXPORTfungsi global . Saat ini saya menjelajahi JavaCpp sebagai opsi, yang menggunakan JNI, tetapi menurut saya vanilla JNI tidak mendukung ini. Apakah ada cara untuk memanggil fungsi anggota C ++ menggunakan vanilla JNI yang saya abaikan?
StockB
1
Silakan lihat di sini stackoverflow.com/questions/20332472/catch-a-double-hotkey
ア レ ッ ク ス
29

Sulit untuk menjawab pertanyaan umum seperti itu. Saya kira perbedaan yang paling jelas adalah bahwa dengan JNI, konversi tipe diimplementasikan di sisi asli dari perbatasan Java / native, sedangkan dengan JNA, konversi tipe diimplementasikan di Java. Jika Anda sudah merasa cukup nyaman dengan pemrograman dalam C dan harus mengimplementasikan sendiri beberapa kode native, saya berasumsi bahwa JNI tampaknya tidak terlalu rumit. Jika Anda seorang programmer Java dan hanya perlu memanggil pustaka asli pihak ketiga, menggunakan JNA mungkin merupakan cara termudah untuk menghindari masalah yang mungkin tidak terlalu jelas dengan JNI.

Meskipun saya tidak pernah membandingkan perbedaan apa pun, saya akan melakukannya karena desainnya, setidaknya anggaplah jenis konversi dengan JNA dalam beberapa situasi akan berkinerja lebih buruk daripada dengan JNI. Misalnya ketika mengoper array, JNA akan mengubahnya dari Java ke native di awal setiap pemanggilan fungsi dan kembali di akhir pemanggilan fungsi. Dengan JNI, Anda dapat mengontrol diri Anda sendiri saat "tampilan" native dari array dibuat, berpotensi hanya membuat tampilan dari bagian array, mempertahankan tampilan di beberapa pemanggilan fungsi dan pada akhirnya melepaskan tampilan tersebut dan memutuskan apakah Anda mau untuk menyimpan perubahan (berpotensi perlu menyalin data kembali) atau membuang perubahan (tidak perlu menyalin). Saya tahu Anda dapat menggunakan array asli di seluruh pemanggilan fungsi dengan JNA menggunakan kelas Memori, tetapi ini juga akan membutuhkan penyalinan memori, yang mungkin tidak diperlukan dengan JNI. Perbedaannya mungkin tidak relevan, tetapi jika tujuan awal Anda adalah meningkatkan kinerja aplikasi dengan menerapkan sebagian darinya dalam kode asli, menggunakan teknologi penghubung yang berkinerja lebih buruk tampaknya bukan pilihan yang paling jelas.

jarnbjo
sumber
8
  1. Anda menulis kode beberapa tahun yang lalu sebelum ada JNA atau menargetkan pra 1.4 JRE.
  2. Kode yang Anda kerjakan tidak ada dalam DLL \ SO.
  3. Anda sedang mengerjakan kode yang tidak kompatibel dengan LGPL.

Itu hanya apa yang bisa saya pikirkan, meskipun saya juga bukan pengguna berat. Sepertinya Anda juga dapat menghindari JNA jika Anda menginginkan antarmuka yang lebih baik daripada yang mereka sediakan tetapi Anda dapat mengkodekannya di java.

stonemetal
sumber
9
Saya tidak setuju tentang 2 - mengubah lib statis menjadi lib dinamis itu mudah. Lihat pertanyaan saya tentang stackoverflow.com/questions/845183/…
jb.
3
Dimulai dengan JNA 4.0, JNA memiliki lisensi ganda di bawah LGPL 2.1 dan Apache License 2.0, dan yang Anda pilih terserah Anda
sbarber2
8

Ngomong-ngomong, di salah satu proyek kami, kami menyimpan footprint JNI yang sangat kecil. Kami menggunakan buffer protokol untuk mewakili objek domain kami dan dengan demikian hanya memiliki satu fungsi asli untuk menjembatani Java dan C (tentu saja fungsi C akan memanggil banyak fungsi lainnya).

Ustaman Sangat
sumber
5
Jadi, alih-alih panggilan metode, kita memiliki pesan yang lewat. Saya telah menginvestasikan sedikit waktu di JNI dan JNA dan juga BridJ, tetapi tidak lama kemudian, semuanya menjadi terlalu menakutkan.
Ustaman Sangat
5

Ini bukan jawaban langsung dan saya tidak memiliki pengalaman dengan JNA tetapi, ketika saya melihat Proyek Menggunakan JNA dan melihat nama-nama seperti SVNKit, IntelliJ IDEA, NetBeans IDE, dll, saya cenderung percaya ini adalah perpustakaan yang cukup baik.

Sebenarnya, saya pasti berpikir saya akan menggunakan JNA daripada JNI ketika saya harus karena memang terlihat lebih sederhana daripada JNI (yang memiliki proses pengembangan yang membosankan). Sayang sekali, JNA tidak dirilis saat ini.

Pascal Thivent
sumber
3

Jika Anda menginginkan kinerja JNI tetapi terhalang oleh kerumitannya, Anda dapat mempertimbangkan untuk menggunakan alat yang menghasilkan pengikatan JNI secara otomatis. Misalnya, JANET (penafian: Saya yang menulisnya) memungkinkan Anda untuk mencampur kode Java dan C ++ dalam satu file sumber, dan misalnya melakukan panggilan dari C ++ ke Java menggunakan sintaks Java standar. Misalnya, inilah cara Anda mencetak string C ke output standar Java:

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

JANET kemudian menerjemahkan Java yang disematkan backtick ke dalam panggilan JNI yang sesuai.

Dawid Kurzyniec
sumber
3

Saya sebenarnya melakukan beberapa benchmark sederhana dengan JNI dan JNA.

Seperti yang telah ditunjukkan orang lain, JNA adalah untuk kenyamanan. Anda tidak perlu mengompilasi atau menulis kode native saat menggunakan JNA. Pemuat pustaka asli JNA juga merupakan salah satu yang terbaik / termudah untuk digunakan yang pernah saya lihat. Sayangnya, tampaknya Anda tidak dapat menggunakannya untuk JNI. (Itulah mengapa saya menulis alternatif untuk System.loadLibrary () yang menggunakan konvensi jalur JNA dan mendukung pemuatan tanpa batas dari classpath (mis. Guci).)

Namun, kinerja JNA bisa jauh lebih buruk daripada JNI. Saya membuat tes yang sangat sederhana yang disebut fungsi peningkatan integer asli sederhana "return arg + 1;". Tolok ukur yang dilakukan dengan jmh menunjukkan bahwa panggilan JNI ke fungsi itu 15 kali lebih cepat daripada JNA.

Contoh yang lebih "kompleks" di mana fungsi native meringkas array integer dari 4 nilai masih menunjukkan bahwa performa JNI 3 kali lebih cepat daripada JNA. Keuntungan yang berkurang mungkin karena cara Anda mengakses array di JNI: contoh saya membuat beberapa hal dan merilisnya lagi selama setiap operasi penjumlahan.

Kode dan hasil tes dapat ditemukan di github .

pengguna1050755
sumber
2

Saya menyelidiki JNI dan JNA untuk perbandingan kinerja karena kami perlu memutuskan salah satu dari mereka untuk memanggil dll dalam proyek dan kami memiliki kendala waktu nyata. Hasilnya menunjukkan bahwa JNI memiliki kinerja yang lebih baik daripada JNA (sekitar 40 kali lipat). Mungkin ada trik untuk kinerja yang lebih baik di JNA tetapi sangat lambat untuk contoh sederhana.

Mustafa Kemal
sumber
1

Kecuali saya melewatkan sesuatu, bukankah perbedaan utama antara JNA vs JNI bahwa dengan JNA Anda tidak dapat memanggil kode Java dari kode native (C)?

Augusto
sumber
6
Kamu bisa. Dengan kelas callback yang sesuai dengan fungsi pointer di sisi C. void register_callback (void (*) (const int)); akan dipetakan ke public static void register_callback (MyCallback arg1); di mana MyCallback adalah antarmuka yang memperluas com.sun.jna.Callback dengan metode tunggal void apply (int value);
Ustaman Sangat
@Augusto ini bisa menjadi komentar juga tolong
swiftBoy
0

Dalam aplikasi khusus saya, JNI terbukti jauh lebih mudah digunakan. Saya perlu membaca dan menulis aliran berkelanjutan ke dan dari port serial - dan tidak ada yang lain. Daripada mencoba mempelajari infrastruktur yang sangat terlibat di JNA, saya merasa jauh lebih mudah untuk membuat prototipe antarmuka asli di Windows dengan DLL tujuan khusus yang mengekspor hanya enam fungsi:

  1. DllMain (diperlukan untuk berinteraksi dengan Windows)
  2. OnLoad (hanya melakukan OutputDebugString jadi saya bisa tahu ketika kode Java dilampirkan)
  3. OnUnload (ditto)
  4. Buka (membuka port, mulai membaca dan menulis utas)
  5. QueueMessage (data antrian untuk keluaran oleh utas tulis)
  6. GetMessage (menunggu dan mengembalikan data yang diterima oleh utas baca sejak panggilan terakhir)
Walter Oney
sumber