Używać JNI zamiast JNA do wywoływania kodu natywnego?

115

JNA wydaje się nieco łatwiejsze w użyciu do wywoływania kodu natywnego w porównaniu z JNI. W jakich przypadkach użyłbyś JNI zamiast JNA?

Marcus Leon
źródło
Jednym z ważnych czynników, dla których wybraliśmy JNA zamiast JNI, jest to, że JNA nie wymagało żadnych modyfikacji naszych natywnych bibliotek. Konieczność dostosowywania natywnych bibliotek tylko po to, aby móc pracować z Javą, była dla nas wielkim NIE.
kvr

Odpowiedzi:

126
  1. JNA nie obsługuje mapowania klas c ++, więc jeśli używasz biblioteki c ++, będziesz potrzebować opakowania jni
  2. Jeśli potrzebujesz dużo kopiowania pamięci. Na przykład wywołujesz jedną metodę, która zwraca ci duży bufor bajtowy, zmieniasz coś w nim, a następnie musisz wywołać inną metodę, która używa tego bufora bajtowego. Wymagałoby to skopiowania tego bufora z c do java, a następnie skopiowania go z powrotem z java do c. W tym przypadku jni wygra pod względem wydajności, ponieważ możesz zachować i zmodyfikować ten bufor w c, bez kopiowania.

Oto problemy, z którymi się spotkałem. Może jest więcej. Ale ogólnie wydajność nie różni się zbytnio między jna i jni, więc gdziekolwiek możesz użyć JNA, używaj go.

EDYTOWAĆ

Ta odpowiedź wydaje się być dość popularna. Oto kilka dodatków:

  1. Jeśli potrzebujesz zmapować C ++ lub COM, istnieje biblioteka autorstwa Olivera Chafica, twórcy JNAerator, o nazwie BridJ . Jest to wciąż młoda biblioteka, ale ma wiele interesujących funkcji:
    • Dynamiczne współdziałanie C / C ++ / COM: wywołanie metod C ++, tworzenie obiektów C ++ (i podklasy klas C ++ z Javy!)
    • Proste odwzorowania typów z dobrym wykorzystaniem typów ogólnych (w tym znacznie ładniejszy model wskaźników)
    • Pełna obsługa JNAerator
    • działa na Windows, Linux, MacOS X, Solaris, Android
  2. Jeśli chodzi o kopiowanie pamięci, uważam, że JNA obsługuje bezpośrednie ByteBuffers, więc można uniknąć kopiowania pamięci.

Tak więc nadal uważam, że wszędzie tam, gdzie to możliwe, lepiej jest używać JNA lub BridJ i powrócić do jni, jeśli wydajność jest krytyczna, ponieważ jeśli musisz często wywoływać funkcje natywne, zauważalny jest spadek wydajności.

Denis Tulskiy
źródło
6
Nie zgadzam się, JNA ma dużo narzutów. Chociaż jego wygoda jest warta wykorzystania w kodzie niekrytycznym czasowo
Gregory Pakosz
3
Radziłbym zachować ostrożność przed użyciem BridJ do projektu na Androida, aby zacytować bezpośrednio ze strony pobierania : „BridJ działa częściowo na emulatorach Android / arm (z SDK), a prawdopodobnie nawet na rzeczywistych urządzeniach (nieprzetestowane).
kizzx2
1
@StockB: jeśli masz bibliotekę z interfejsem API, w której musisz przekazywać obiekty C ++ lub wywoływać metody na obiekcie C ++, JNA nie może tego zrobić. Może wywoływać tylko metody waniliowe C.
Denis Tulskiy,
2
O ile rozumiem, JNI może wywoływać tylko JNIEXPORTfunkcje globalne . Obecnie badam JavaCpp jako opcję, która używa JNI, ale nie sądzę, aby wanilia JNI to obsługiwała. Czy istnieje sposób wywoływania funkcji składowych C ++ przy użyciu waniliowego JNI, którego przeoczam?
StockB
29

Trudno odpowiedzieć na tak ogólne pytanie. Przypuszczam, że najbardziej oczywistą różnicą jest to, że w przypadku JNI konwersja typów jest implementowana po natywnej stronie granicy Java / native, podczas gdy w przypadku JNA konwersja typów jest zaimplementowana w Javie. Jeśli już czujesz się dobrze z programowaniem w C i musisz samodzielnie zaimplementować kod natywny, założyłbym, że JNI nie wydaje się zbyt skomplikowane. Jeśli jesteś programistą Java i potrzebujesz tylko wywołać natywną bibliotekę innej firmy, użycie JNA jest prawdopodobnie najłatwiejszą drogą do uniknięcia być może nie tak oczywistych problemów z JNI.

Chociaż nigdy nie testowałem żadnych różnic, ze względu na projekt, przynajmniej przypuszczam, że konwersja typów z JNA w niektórych sytuacjach będzie działać gorzej niż z JNI. Na przykład podczas przekazywania tablic JNA konwertuje je z języka Java na natywne na początku każdego wywołania funkcji iz powrotem na końcu wywołania funkcji. Dzięki JNI możesz kontrolować siebie, kiedy generowany jest natywny „widok” tablicy, potencjalnie tworząc tylko widok części tablicy, zachować widok na kilka wywołań funkcji, a na koniec zwolnić widok i zdecydować, czy chcesz aby zachować zmiany (potencjalnie wymagające skopiowania danych z powrotem) lub odrzucić zmiany (kopiowanie nie jest wymagane). Wiem, że możesz używać natywnej tablicy w wywołaniach funkcji z JNA przy użyciu klasy Memory, ale będzie to również wymagało kopiowania pamięci, co może być niepotrzebne w przypadku JNI. Różnica może nie mieć znaczenia, ale jeśli Twoim pierwotnym celem jest zwiększenie wydajności aplikacji poprzez implementację jej części w kodzie natywnym, użycie gorszej technologii mostu nie wydaje się być najbardziej oczywistym wyborem.

jarnbjo
źródło
8
  1. Piszesz kod kilka lat temu przed pojawieniem się JNA lub celujesz w JRE starsze niż 1.4.
  2. Kod, z którym pracujesz, nie znajduje się w bibliotece DLL \ SO.
  3. Pracujesz na kodzie, który jest niezgodny z LGPL.

To tylko to, co mogę wymyślić z czubka głowy, chociaż nie jestem zbyt ciężkim użytkownikiem. Wydaje się również, że możesz uniknąć JNA, jeśli chcesz mieć lepszy interfejs niż ten, który zapewniają, ale możesz go kodować w Javie.

kamień kamienny
źródło
9
Nie zgadzam się co do 2 - konwersja biblioteki statycznej do biblioteki dynamicznej jest łatwa. Zobacz moje pytanie abput it stackoverflow.com/questions/845183/ ...
jb.
3
Począwszy od JNA 4.0, JNA jest objęty podwójną licencją zarówno na licencji LGPL 2.1, jak i Apache 2.0, a wybór należy do Ciebie
sbarber2
8

Nawiasem mówiąc, w jednym z naszych projektów zachowaliśmy bardzo mały ślad stopy JNI. Użyliśmy buforów protokołów do reprezentowania naszych obiektów domeny i dlatego mieliśmy tylko jedną natywną funkcję łączącą Javę i C (wtedy oczywiście ta funkcja C wywoływałaby kilka innych funkcji).

Ustaman Sangat
źródło
5
Tak więc zamiast wywołania metody mamy przekazywanie wiadomości. Zainwestowałem sporo czasu w JNI i JNA, a także w BridJ, ale wkrótce wszystkie stają się zbyt przerażające.
Ustaman Sangat
5

Nie jest to bezpośrednia odpowiedź i nie mam doświadczenia z JNA, ale kiedy patrzę na projekty wykorzystujące JNA i widzę nazwy takie jak SVNKit, IntelliJ IDEA, NetBeans IDE, itp., Wierzę, że jest to całkiem przyzwoita biblioteka.

Właściwie, zdecydowanie myślę, że użyłbym JNA zamiast JNI, kiedy musiałem, ponieważ wygląda to na prostsze niż JNI (który ma nudny proces programowania). Szkoda, JNA nie została wydana w tym czasie.

Pascal Thivent
źródło
3

Jeśli chcesz wydajności JNI, ale zniechęca Cię jego złożoność, możesz rozważyć użycie narzędzi, które automatycznie generują powiązania JNI. Na przykład JANET (zastrzeżenie: napisałem to) pozwala na mieszanie kodu Java i C ++ w jednym pliku źródłowym, a także np. Wykonywanie wywołań z C ++ do Javy przy użyciu standardowej składni Javy. Na przykład, oto jak wypisać ciąg C na standardowe wyjście Java:

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

Następnie JANET tłumaczy język Java z wbudowanym znacznikiem wstecznym na odpowiednie wywołania JNI.

Dawid Kurzyniec
źródło
3

Właściwie zrobiłem kilka prostych testów porównawczych z JNI i JNA.

Jak inni już zauważyli, JNA jest dla wygody. Nie musisz kompilować ani pisać kodu natywnego, gdy używasz JNA. Natywny program ładujący biblioteki JNA jest również jednym z najlepszych / najłatwiejszych w użyciu, jakie kiedykolwiek widziałem. Niestety, wydaje się, że nie możesz go używać do JNI. (Dlatego napisałem alternatywę dla System.loadLibrary (), która korzysta z konwencji ścieżki JNA i obsługuje bezproblemowe ładowanie ze ścieżki klas (tj. Słoików).)

Jednak wydajność JNA może być znacznie gorsza niż JNI. Zrobiłem bardzo prosty test, który nazwał prostą natywną funkcję zwiększającą liczbę całkowitą „return arg + 1;”. Benchmarki wykonane z jmh pokazały, że wywołania JNI do tej funkcji są 15 razy szybsze niż JNA.

Bardziej „złożony” przykład, w którym funkcja natywna sumuje tablicę liczb całkowitych składającą się z 4 wartości, nadal pokazuje, że wydajność JNI jest 3 razy szybsza niż JNA. Zmniejszona korzyść wynikała prawdopodobnie z tego, jak uzyskujesz dostęp do tablic w JNI: mój przykład utworzył kilka rzeczy i zwolnił je ponownie podczas każdej operacji sumowania.

Kod i wyniki testów można znaleźć na github .

user1050755
źródło
2

Zbadałem JNI i JNA w celu porównania wydajności, ponieważ musieliśmy zdecydować, że jeden z nich wywoła bibliotekę dll w projekcie i mieliśmy ograniczenie czasu rzeczywistego. Wyniki pokazały, że JNI ma większą wydajność niż JNA (około 40 razy). Być może istnieje sztuczka zapewniająca lepszą wydajność w JNA, ale na prostym przykładzie jest bardzo powolna.

Mustafa Kemal
źródło
1

O ile czegoś nie brakuje, czy główna różnica między JNA a JNI jest taka, że ​​z JNA nie można wywołać kodu Java z kodu natywnego (C)?

Augusto
źródło
6
Możesz. Z klasą wywołania zwrotnego, która odpowiada wskaźnikowi funkcji po stronie C. void register_callback (void (*) (const int)); zostanie zmapowany do publicznego statycznego natywnego void register_callback (MyCallback arg1); gdzie MyCallback jest interfejsem rozszerzającym com.sun.jna.Callback za pomocą jednej metody void apply (int value);
Ustaman Sangat
@Augusto to również może być komentarz
swiftBoy
0

W mojej aplikacji JNI okazał się dużo łatwiejszy w użyciu. Musiałem czytać i zapisywać ciągłe strumienie do iz portu szeregowego - i nic więcej. Zamiast próbować nauczyć się bardzo skomplikowanej infrastruktury JNA, okazało się, że znacznie łatwiej było stworzyć prototyp natywnego interfejsu w systemie Windows za pomocą specjalnej biblioteki DLL, która wyeksportowała tylko sześć funkcji:

  1. DllMain (wymagany do współpracy z systemem Windows)
  2. OnLoad (po prostu wykonuje OutputDebugString, dzięki czemu mogę wiedzieć, kiedy dołączany jest kod Java)
  3. OnUnload (jw.)
  4. Otwórz (otwiera port, rozpoczyna odczytywanie i zapisywanie wątków)
  5. QueueMessage (kolejkuje dane do wyjścia przez wątek zapisu)
  6. GetMessage (czeka na i zwraca dane odebrane przez odczytany wątek od ostatniego wywołania)
Walter Oney
źródło