Використовувати JNI замість JNA для виклику рідного коду?


115

JNA, здається, трохи простіше використовувати для виклику нативного коду порівняно з JNI. У яких випадках ви б використовували JNI над JNA?


Одним важливим фактором, який ми вибрали JNA над JNI, є те, що JNA не потребувала жодних змін у наших рідних бібліотеках. Потрібно налаштувати рідні бібліотеки, щоб мати можливість працювати з Java, для нас було великим НІ.
kvr

Відповіді:


126
  1. JNA не підтримує відображення класів c ++, тому якщо ви використовуєте бібліотеку c ++, вам знадобиться оболонка jni
  2. Якщо вам потрібно багато копіювання пам'яті. Наприклад, ви викликаєте один метод, який повертає вам великий байт-буфер, ви щось змінюєте в ньому, тоді вам потрібно викликати інший метод, який використовує цей байт-буфер. Для цього знадобиться скопіювати цей буфер з c на java, а потім скопіювати його назад з java в c. У цьому випадку jni виграє в продуктивності, оскільки ви можете зберігати та змінювати цей буфер в c, не копіюючи.

Це проблеми, з якими я стикався. Можливо, є і більше. Але загалом продуктивність не відрізняється від jna та jni, тому де б ви не могли використовувати JNA, використовуйте її.

EDIT

Ця відповідь здається досить популярною. Ось ось кілька доповнень:

  1. Якщо вам потрібно зіставити C ++ або COM, є бібліотека Олівера Чафіка, творця JNAerator, що називається BridJ . Це все ще молода бібліотека, але вона має багато цікавих особливостей:
    • Динамічний взаємозв'язок C / C ++ / COM: викликайте методи C ++, створюйте об'єкти C ++ (і підклас класів C ++ з Java!)
    • Відображення прямого типу з хорошим використанням дженерики (включаючи набагато більш приємну модель для покажчиків)
    • Повна підтримка JNAerator
    • працює в Windows, Linux, MacOS X, Solaris, Android
  2. Що стосується копіювання в пам'ять, я вважаю, що JNA підтримує прямі ByteBuffers, тому копіювання пам'яті можна уникнути.

Отже, я все ще вважаю, що там, де це можливо, краще використовувати JNA або BridJ, і повернутись до jni, якщо продуктивність є критичною, тому що якщо вам потрібно часто дзвонити на рідні функції, помітний удар.


6
Я не погоджуюсь, у JNA дуже багато витрат. Хоча його зручність варто використати у критичному за часом часі
Грегорі Пакош

3
Я б радив бути обережним, перш ніж використовувати BridJ для проекту Android, цитувати безпосередньо зі сторінки завантаження : "BridJ частково працює на Android / емуляторах arm (з SDK), і, ймовірно, навіть на фактичних пристроях (неперевірений) ".
kizzx2

1
@StockB: якщо у вас є бібліотека з api, куди вам потрібно передати об'єкти C ++, або зателефонувати на об'єкт C ++, JNA не може цього зробити. Це може викликати лише методи ванілі С.
Денис Тульський

2
Наскільки я розумію, JNI може викликати лише глобальні JNIEXPORTфункції. Зараз я досліджую JavaCpp як варіант, який використовує JNI, але я не думаю, що ванільний JNI це підтримує. Чи є спосіб викликати функції C ++, використовуючи ванільний JNI, який я переглядаю?
StockB


29

На таке загальне запитання важко відповісти. Я вважаю, що найбільш очевидна відмінність полягає в тому, що при JNI перетворення типів здійснюється на рідній стороні Java / рідної межі, тоді як при JNA перетворення типів здійснюється в Java. Якщо ви вже відчуваєте себе досить комфортно з програмуванням на C і вам доведеться самостійно реалізувати якийсь нативний код, я вважаю, що JNI не здасться занадто складним. Якщо ви Java-програміст і вам потрібно посилатися лише на сторонню рідну бібліотеку, використання JNA - це, мабуть, найпростіший шлях, щоб уникнути можливо не настільки очевидних проблем з JNI.

Хоча я ніколи не визначав жодних відмінностей, я б, зважаючи на дизайн, принаймні припускав, що перетворення типу з JNA в деяких ситуаціях буде гірше, ніж з JNI. Наприклад, передаючи масиви, JNA буде перетворювати їх з Java в початкові на початку кожного виклику функції і назад в кінці виклику функції. За допомогою JNI ви можете керувати собою, коли генерується нативний "перегляд" масиву, потенційно створюючи лише перегляд частини масиву, зберігати подання через кілька викликів функцій, а в кінці звільняти подання та вирішувати, чи хочете ви щоб зберегти зміни (потенційно вимагати копіювання даних назад) або відхилити зміни (копія не потрібна). Я знаю, що ви можете використовувати власний масив для функціональних викликів з JNA за допомогою класу Memory, але для цього також буде потрібно копіювання пам'яті, що може бути непотрібним для JNI. Різниця може не бути актуальною, але якщо ваша первісна мета - збільшити продуктивність додатків, реалізуючи його частини в кодовому коді, використання гірших технологій мосту здається не найбільш очевидним вибором.


8
  1. Ви пишете код кілька років тому, перш ніж був JNA або націлений на попередній JRE.
  2. Код, з яким ви працюєте, не міститься в DLL \ SO.
  3. Ви працюєте над кодом, несумісним з LGPL.

Це тільки те, що я можу придумати вгорі голови, хоча я і не є важким користувачем. Також здається, що ти можеш уникнути JNA, якби ти хотів кращого інтерфейсу, ніж той, який вони надають, але ти можеш кодувати навколо цього в Java.


9
Я не згоден з приводу 2 - перетворити статичну лібу в динамічну лібу дуже просто. Дивіться моє запитання abput it stackoverflow.com/questions/845183/…
jb.

3
Починаючи з JNA 4.0, JNA має подвійну ліцензію як під LGPL 2.1, так і за ліцензією Apache 2.0, і яку ви обираєте, вирішувати вам
sbarber2

8

До речі, в одному з наших проектів ми зберегли дуже маленький друк JNI. Ми використовували буфери протоколів для представлення наших об’єктів домену і, таким чином, мали лише одну нативну функцію, щоб з'єднати Java та C (тоді, звичайно, ця функція C викликала б купу інших функцій).


5
Отже, замість виклику методу у нас є передача повідомлення. Я вклав зовсім небагато часу в JNI та JNA, а також BridJ, але досить скоро вони всі стають занадто страшними.
Устаман Сангат

5

Це не пряма відповідь, і я не маю досвіду роботи з JNA, але, коли я дивлюся на проекти, що використовують JNA, і бачу такі назви, як SVNKit, IntelliJ IDEA, NetBeans IDE тощо, я схильний вважати, що це досить пристойна бібліотека.

Насправді я, безумовно, думаю, що використовував би JNA замість JNI, коли мені довелося, як це справді виглядає простіше, ніж JNI (який має нудний процес розвитку). Шкода, що JNA наразі не була звільнена.


3

Якщо ви хочете, щоб продуктивність JNI була вражена її складністю, ви можете скористатися інструментами, які генерують прив'язки JNI автоматично. Наприклад, JANET (відмова від відповідальності: я написав це) дозволяє змішувати Java та код C ++ в одному вихідному файлі, наприклад, здійснювати дзвінки з C ++ на Java, використовуючи стандартний синтаксис Java. Наприклад, ось як ви надрукували рядок C на стандартний вихід Java:

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

Потім JANET переводить Java-вбудовану Java у відповідні дзвінки JNI.


3

Я фактично робив кілька простих орієнтирів з JNI та JNA.

Як вже вказували інші, JNA - це для зручності. Вам не потрібно збирати або писати нативний код під час використання JNA. Навантажувач рідної бібліотеки JNA також є одним з найкращих / найпростіших у користуванні, які я коли-небудь бачив. На жаль, ви не можете використовувати його для JNI, здається. (Тому я написав альтернативу для System.loadLibrary (), яка використовує конвенцію контуру JNA та підтримує безперешкодне завантаження з classpath (тобто, банки).)

Однак продуктивність JNA може бути набагато гіршою, ніж у JNI. Я зробив дуже простий тест, який назвав просту функцію приросту цілого числа "повернути arg + 1;". Оцінки, зроблені за допомогою jmh, показали, що виклики JNI на цю функцію в 15 разів швидше, ніж JNA.

Більш "складний" приклад, коли нативна функція підсумовує цілий масив із 4 значень, все ще показала, що продуктивність JNI в 3 рази швидша, ніж JNA. Зменшена перевага, мабуть, пояснюється тим, як ви отримуєте доступ до масивів в JNI: мій приклад створив деякі речі та випустив їх знову під час кожної операції підбиття підсумків.

Код та результати тестування можна знайти на сайті github .


2

Я досліджував JNI та JNA для порівняння продуктивності, тому що нам потрібно було вирішити одну з них, щоб викликати dll в проекті, і ми мали обмеження в реальному часі. Результати показали, що JNI має більшу ефективність, ніж JNA (приблизно в 40 разів). Можливо, є хитрість для кращої роботи в JNA, але це дуже повільно для простого прикладу.


1

Якщо я чогось не пропускаю, чи не головна відмінність JNA від JNI в тому, що з JNA ви не можете зателефонувати на код Java з нативного (C) коду?


6
Ти можеш. З класом зворотного виклику, який відповідає покажчику функції на стороні С. void register_callback (void (*) (const int)); буде відображено в загальнодоступну статичну природну порожнечу register_callback (MyCallback arg1); де MyCallback - це інтерфейс, що розширює com.sun.jna.Callback, застосовуючи єдиний метод void (значення int);
Устаман Сангат

@Августо це може бути коментар і будь ласка
swiftBoy

0

У моєму конкретному застосуванні JNI виявився набагато простішим у використанні. Мені потрібно було читати і записувати безперервні потоки до і з послідовного порту - і більше нічого. Замість того, щоб спробувати вивчити дуже задіяну інфраструктуру в JNA, мені було набагато простіше прототипувати власний інтерфейс у Windows із спеціальною DLL-програмою, яка експортувала лише шість функцій:

  1. DllMain (необхідний для взаємодії з Windows)
  2. OnLoad (просто робить OutputDebugString, щоб я міг знати, коли додається код Java)
  3. OnUnload (ditto)
  4. Відкрити (відкриває порт, починає читати та писати теми)
  5. QueueMessage (дані черг для виводу потоком запису)
  6. GetMessage (чекає та повертає дані, отримані прочитаною ниткою з моменту останнього дзвінка)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.