Yerel kodu çağırmak için JNA yerine JNI kullanılsın mı?


115

JNA, JNI'ye kıyasla yerel kodu aramak için biraz daha kolay görünüyor. Hangi durumlarda JNA yerine JNI kullanırsınız?


JNI yerine JNA'yı seçtiğimiz önemli bir faktör, JNA'nın yerel kütüphanelerimizde herhangi bir değişiklik gerektirmemesidir. Yalnızca Java ile çalışabilmek için yerel kitaplıkları özelleştirmek zorunda olmak bizim için büyük bir HAYIR oldu.
kvr

Yanıtlar:


126
  1. JNA, c ++ sınıflarının eşleştirilmesini desteklemez, bu nedenle c ++ kitaplığı kullanıyorsanız bir jni sarmalayıcısına ihtiyacınız olacaktır.
  2. Çok fazla bellek kopyalamaya ihtiyacınız varsa. Örneğin, size büyük bir bayt tamponu döndüren bir yöntemi çağırırsınız, içindeki bir şeyi değiştirirsiniz, sonra bu bayt arabelleğini kullanan başka bir yöntemi çağırmanız gerekir. Bu, bu arabelleği c'den java'ya kopyalamanızı, ardından java'dan c'ye geri kopyalamanızı gerektirir. Bu durumda jni performansta kazanacaktır çünkü bu tamponu kopyalamadan c'de tutabilir ve değiştirebilirsiniz.

Karşılaştığım sorunlar bunlar. Belki daha fazlası vardır. Ancak genel olarak performans jna ve jni arasında o kadar da farklı değildir, bu yüzden JNA'yı nerede kullanırsanız kullanın onu kullanın.

DÜZENLE

Bu cevap oldukça popüler görünüyor. İşte bazı eklemeler:

  1. C ++ veya COM'u eşlemeniz gerekiyorsa, JNAerator'ün yaratıcısı Oliver Chafic'in BridJ adında bir kitaplığı var . Hala genç bir kütüphane, ancak birçok ilginç özelliği var:
    • Dinamik C / C ++ / COM birlikte çalışma: C ++ yöntemlerini çağırın, C ++ nesneleri (ve Java'dan C ++ alt sınıfları oluşturun!)
    • İyi jenerik kullanımı ile basit tip eşlemeleri (İşaretçiler için çok daha hoş model dahil)
    • Tam JNAerator desteği
    • Windows, Linux, MacOS X, Solaris, Android'de çalışır
  2. Bellek kopyalamaya gelince, JNA'nın doğrudan ByteBuffers'ı desteklediğine inanıyorum, böylece bellek kopyalamadan kaçınılabilir.

Bu yüzden, hala mümkün olan her yerde JNA veya BridJ kullanmanın ve performans kritikse jni'ye dönmenin daha iyi olduğuna inanıyorum, çünkü yerel işlevleri sık sık çağırmanız gerekiyorsa, performans düşüşü fark edilir.


6
Katılmıyorum, JNA'nın çok ek yükü var.
Kolaylığı

3
BridJ'i bir Android projesi için kullanmadan önce, doğrudan indirme sayfasından alıntı yapmamızı tavsiye ederim : "BridJ kısmen Android / arm emülatörlerinde (SDK ile) ve hatta muhtemelen gerçek cihazlarda (test edilmemiş) çalışıyor. "
kizzx2

1
@StockB: C ++ nesnelerini iletmeniz gereken api içeren bir kitaplığınız varsa veya C ++ nesnesinde yöntemler çağırıyorsanız, JNA bunu yapamaz. Yalnızca vanilya C yöntemlerini çağırabilir.
Denis Tulskiy

2
Anladığım kadarıyla JNI yalnızca global JNIEXPORTişlevleri çağırabilir . Şu anda JNI kullanan bir seçenek olarak JavaCpp'yi araştırıyorum, ancak vanilya JNI'nin bunu desteklediğini düşünmüyorum. Göz ardı ettiğim vanilya JNI kullanarak C ++ üye işlevlerini çağırmanın bir yolu var mı?
StockB


29

Böyle genel bir soruyu yanıtlamak zor. Sanırım en bariz fark, JNI ile tür dönüştürme Java / yerel sınırın yerel tarafında gerçekleştirilirken, JNA ile tür dönüştürme Java'da gerçekleştirilir. C'de programlama konusunda oldukça rahat hissediyorsanız ve bazı yerel kodları kendiniz uygulamanız gerekiyorsa, JNI'nin çok karmaşık görünmeyeceğini varsayıyorum. Bir Java programcısıysanız ve yalnızca üçüncü taraf bir yerel kitaplığı çağırmanız gerekiyorsa, JNA'yı kullanmak muhtemelen JNI ile ilgili pek de açık olmayan sorunlardan kaçınmanın en kolay yoludur.

Asla herhangi bir farklılığı kıyaslamamış olsam da, tasarım nedeniyle yapardım, en azından bazı durumlarda JNA ile tip dönüşümünün JNI'den daha kötü performans göstereceğini varsayalım. Örneğin dizileri iletirken, JNA bunları her işlev çağrısının başında ve işlev çağrısının sonunda geri dönerek Java'dan yerel olana dönüştürecektir. JNI ile, dizinin yerel bir "görünümü" oluşturulduğunda kendinizi kontrol edebilirsiniz, potansiyel olarak yalnızca dizinin bir kısmının bir görünümünü oluşturabilir, görünümü birkaç işlev çağrısında tutabilir ve sonunda görünümü serbest bırakıp isteyip istemediğinize karar verebilirsiniz. değişiklikleri saklamak (potansiyel olarak verilerin geri kopyalanmasını gerektirir) veya değişiklikleri iptal etmek (kopyası gerekmez). Memory sınıfını kullanarak JNA ile işlev çağrılarında yerel bir dizi kullanabileceğinizi biliyorum, ancak bu aynı zamanda bellek kopyalamayı da gerektirecektir. JNI için gereksiz olabilir. Aradaki fark alakalı olmayabilir, ancak asıl hedefiniz uygulama performansını bazı kısımlarını yerel kodda uygulayarak artırmaksa, daha kötü performans gösteren bir köprü teknolojisi kullanmak en bariz seçenek gibi görünmüyor.


8
  1. Birkaç yıl önce JNA olmadan önce kod yazıyorsunuz veya 1.4 öncesi bir JRE'yi hedefliyorsunuz.
  2. Üzerinde çalıştığınız kod bir DLL \ SO içinde değil.
  3. LGPL ile uyumlu olmayan kod üzerinde çalışıyorsunuz.

Ben ikisinin de ağır bir kullanıcısı olmasam da, sadece kafamın tepesinde bulabildiğim şey bu. Ayrıca, sağladıklarından daha iyi bir arayüz istiyorsanız, JNA'dan kaçınabileceğiniz gibi görünüyor, ancak bunun etrafında java'da kodlama yapabilirsiniz.


9
2'ye katılmıyorum - statik kitaplığı dinamik kitaplığa dönüştürmek kolaydır. Benim soruma bakın stackoverflow.com/questions/845183/…
jb.

3
JNA 4.0'dan başlayarak, JNA hem LGPL 2.1 hem de Apache License 2.0 kapsamında çift lisanslıdır ve sizin seçiminiz size kalmış
sbarber2

8

Bu arada, projelerimizden birinde çok küçük bir JNI ayak izi tuttuk. Etki alanı nesnelerimizi temsil etmek için protokol arabellekleri kullandık ve bu nedenle Java ve C'yi köprülemek için yalnızca bir yerel işleve sahip olduk (bu durumda C işlevi, bir dizi başka işlevi çağırırdı).


5
Bu nedenle, bir yöntem çağrısı yerine mesaj geçişimiz var. JNI ve JNA'ya ve ayrıca BridJ'e oldukça fazla zaman ayırdım, ancak çok geçmeden hepsi biraz fazla korkutuyor.
Ustaman Sangat

5

Bu doğrudan bir cevap değil ve JNA ile deneyimim yok ama JNA Kullanan Projelere baktığımda ve SVNKit, IntelliJ IDEA, NetBeans IDE gibi isimler gördüğümde, bunun oldukça iyi bir kitaplık olduğuna inanmaya meyilliyim.

Aslında, JNI'den (sıkıcı bir geliştirme süreci olan) daha basit göründüğü için, mecbur kaldığımda JNI yerine JNA kullanacağımı kesinlikle düşünüyorum. Çok kötü, JNA şu anda serbest bırakılmadı.


3

JNI performansı istiyorsanız, ancak karmaşıklığından korkuyorsanız, JNI bağlamalarını otomatik olarak oluşturan araçları kullanmayı düşünebilirsiniz. Örneğin, JANET (sorumluluk reddi: Ben yazdım) tek bir kaynak dosyada Java ve C ++ kodunu karıştırmanıza ve örneğin standart Java sözdizimini kullanarak C ++ 'dan Java'ya çağrı yapmanıza izin verir. Örneğin, Java standart çıktısına bir C dizesini şu şekilde yazdırabilirsiniz:

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

JANET daha sonra gömülü backtick Java'yı uygun JNI çağrılarına çevirir.


3

Aslında JNI ve JNA ile bazı basit kıyaslamalar yaptım.

Başkalarının da belirttiği gibi, JNA kolaylık sağlamak içindir. JNA'yı kullanırken yerel kodu derlemenize veya yazmanıza gerek yoktur. JNA'nın yerel kütüphane yükleyicisi de şimdiye kadar gördüğüm en iyi / kullanımı en kolay olanlardan biri. Ne yazık ki, JNI için kullanamazsınız öyle görünüyor. (Bu nedenle , System.loadLibrary () için JNA'nın yol kuralını kullanan ve sınıf yolundan (yani kavanozlardan) sorunsuz yüklemeyi destekleyen bir alternatif yazdım .)

Ancak JNA'nın performansı, JNI'ninkinden çok daha kötü olabilir. Basit bir yerel tamsayı artış işlevi "return arg + 1;" olarak adlandırılan çok basit bir test yaptım. Jmh ile yapılan karşılaştırmalar, bu işleve JNI çağrılarının JNA'dan 15 kat daha hızlı olduğunu gösterdi.

Yerel işlevin 4 değerden oluşan bir tamsayı dizisini topladığı daha "karmaşık" bir örnek, JNI performansının JNA'dan 3 kat daha hızlı olduğunu gösterdi. Azaltılmış avantaj muhtemelen JNI'deki dizilere nasıl eriştiğinizden kaynaklanıyordu: Örneğim bazı şeyler yarattı ve her toplama işlemi sırasında yeniden yayınladı.

Kod ve test sonuçları github adresinde bulunabilir .


2

Performans karşılaştırması için JNI ve JNA'yı araştırdım çünkü bunlardan birinin projede dll'yi çağırmasına karar vermemiz gerekiyordu ve gerçek zamanlı bir kısıtlamamız vardı. Sonuçlar, JNI'nin JNA'dan daha yüksek performansa sahip olduğunu göstermiştir (yaklaşık 40 kat). Belki JNA'da daha iyi performans için bir numara vardır, ancak basit bir örnek için çok yavaştır.


1

Bir şeyi kaçırmadığım sürece, JNA ile JNI arasındaki temel fark, JNA ile Java kodunu yerel (C) koddan çağıramazsınız değil mi?


6
Yapabilirsin. C tarafındaki bir işlev işaretçisine karşılık gelen bir geri çağrı sınıfı ile. void register_callback (void (*) (const int)); genel statik yerel void register_callback (MyCallback arg1) ile eşlenir; MyCallback, com.sun.jna.Callback'i tek bir yöntemle genişleten bir arabirim void apply (int değeri);
Ustaman Sangat

@Augusto bu da bir yorum olabilir lütfen
swiftBoy

0

Benim özel uygulamamda, JNI kullanımının çok daha kolay olduğunu kanıtladı. Seri bağlantı noktasından sürekli akışları okuyup yazmam gerekiyordu - başka hiçbir şey yoktu. JNA'daki çok ilgili altyapıyı öğrenmeye çalışmak yerine, Windows'ta yerel arabirimi yalnızca altı işlevi dışa aktaran özel amaçlı bir DLL ile prototiplemeyi çok daha kolay buldum:

  1. DllMain (Windows ile arayüz için gereklidir)
  2. OnLoad (sadece bir OutputDebugString yapar, böylece Java kodunun ne zaman eklendiğini anlayabilirim)
  3. OnUnload (aynen)
  4. Aç (bağlantı noktasını açar, konuları okumaya ve yazmaya başlar)
  5. QueueMessage (yazma iş parçacığı tarafından çıktı için verileri kuyruğa alır)
  6. GetMessage (son çağrıdan bu yana okuma iş parçacığı tarafından alınan verileri bekler ve döndürür)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.