Использовать 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.

РЕДАКТИРОВАТЬ

Этот ответ кажется довольно популярным. Итак, вот некоторые дополнения:

  1. Если вам нужно сопоставить C ++ или COM, существует библиотека BridJ, созданная Оливером Чафиком, создателем JNAerator . Это еще молодая библиотека, но в ней есть много интересных особенностей:
    • Динамическое взаимодействие 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 / рука эмуляторы (с SDK), и , возможно , даже на реальных устройствах (непроверенные). »
kizzx2
1
@StockB: если у вас есть библиотека с api, в которой вы должны передавать объекты C ++ или вызывать методы объекта C ++, JNA не может этого сделать. Он может вызывать только ванильные методы C.
Денис Тульский
2
Насколько я понимаю, JNI может вызывать только глобальные JNIEXPORTфункции. В настоящее время я изучаю JavaCpp как вариант, который использует JNI, но я не думаю, что ванильный JNI поддерживает это. Есть ли способ вызвать функции-члены C ++ с использованием ванильного JNI, на который я не обращаю внимания?
StockB
1
Загляните сюда stackoverflow.com/questions/20332472/catch-a-double-hotkey
ア レ ッ ク ス
29

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

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

jarnbjo
источник
8
  1. Вы пишете код несколько лет назад, до появления JNA, или ориентируетесь на JRE до 1.4.
  2. Код, с которым вы работаете, не находится в DLL \ SO.
  3. Вы работаете над кодом, несовместимым с LGPL.

Это только то, что я могу придумать в голове, хотя я и не являюсь активным пользователем. Также кажется, что вы могли бы избежать JNA, если бы вам нужен лучший интерфейс, чем тот, который они предоставляют, но вы можете кодировать это в java.

stonemetal
источник
9
Я не согласен с 2 - преобразовать статическую библиотеку в динамическую легко. См. Мой вопрос об этом stackoverflow.com/questions/845183/…
jb.
3
Начиная с JNA 4.0, JNA имеет двойную лицензию под LGPL 2.1 и Apache License 2.0, и выбор зависит от вас
sbarber2 05
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 со встроенной обратной кавычкой в ​​соответствующие вызовы JNI.

Давид Курзинец
источник
3

Я действительно провел несколько простых тестов с JNI и JNA.

Как уже отмечали другие, JNA предназначена для удобства. При использовании JNA вам не нужно компилировать или писать собственный код. Загрузчик собственных библиотек JNA также является одним из лучших / самых простых в использовании, которые я когда-либо видел. К сожалению, похоже, вы не можете использовать его для JNI. (Вот почему я написал альтернативу для System.loadLibrary () которая использует соглашение о путях JNA и поддерживает бесшовную загрузку из пути к классам (т.е. jar-файлов).)

Однако производительность JNA может быть намного хуже, чем у JNI. Я провел очень простой тест, который вызвал простую встроенную функцию целочисленного приращения «return arg + 1;». Тесты, проведенные с помощью jmh, показали, что вызовы этой функции JNI в 15 раз быстрее, чем JNA.

Более «сложный» пример, где собственная функция суммирует целочисленный массив из 4 значений, все же показал, что производительность JNI в 3 раза выше, чем у JNA. Уменьшенное преимущество, вероятно, связано с тем, как вы получаете доступ к массивам в JNI: в моем примере были созданы некоторые вещи и снова выпущены во время каждой операции суммирования.

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

user1050755
источник
2

Я исследовал JNI и JNA для сравнения производительности, потому что нам нужно было выбрать один из них для вызова dll в проекте, а у нас было ограничение по времени. Результаты показали, что JNI имеет большую производительность, чем JNA (примерно в 40 раз). Возможно, в JNA есть способ повысить производительность, но для простого примера он очень медленный.

Мустафа Кемаль
источник
1

Если я чего-то не упускаю, разве главное отличие JNA от JNI в том, что с JNA вы не можете вызывать Java-код из собственного (C) кода?

Аугусто
источник
6
Ты можешь. С классом обратного вызова, который соответствует указателю функции на стороне C. void register_callback (void (*) (const int)); будет отображаться на общедоступный статический собственный void register_callback (MyCallback arg1); где MyCallback - это интерфейс, расширяющий com.sun.jna.Callback с помощью одного метода void apply (int value);
Ustaman Sangat
@Augusto, это тоже может быть комментарий, пожалуйста
swiftBoy
0

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

  1. DllMain (требуется для взаимодействия с Windows)
  2. OnLoad (просто выполняет OutputDebugString, чтобы я мог знать, когда прикрепляется код Java)
  3. OnUnload (то же самое)
  4. Открыть (открывает порт, запускает потоки чтения и записи)
  5. QueueMessage (ставит данные в очередь для вывода потоком записи)
  6. GetMessage (ожидает и возвращает данные, полученные потоком чтения с момента последнего вызова)
Уолтер Оней
источник