Когда использовать JNIEXPORT и JNICALL в Android NDK?

Я пытаюсь написать свои собственные источники jni. Глядя на некоторые образцы ndk, я обнаружил, что они часто используют те макросы JNIEXPORT и JNICALL, которые описываются именем пакета java, как этот

JNIEXPORT void JNICALL Java_com_example_plasma_PlasmaView_renderPlasma(JNIEnv * env, jobject obj, jobject bitmap, jlong time_ms)

Я googled, но я не могу понять, когда и как использовать эти макросы

Ответы

Ответ 1

JNIEXPORT и JNICALL определены в NDK_ROOT/platform/android-9/arch-arm/usr/include/jni.h. В зависимости от вашей установки этот путь будет другим, но в основном похожим.

#define JNIIMPORT
#define JNIEXPORT  __attribute__ ((visibility ("default")))
#define JNICALL

JNIEXPORT используется для создания собственных функций в динамической таблице встроенного двоичного файла (*.so). Они могут быть установлены как "скрытые" или "по умолчанию" (подробнее здесь). Если эти функции не находятся в динамической таблице, JNI не сможет найти функции для их вызова, поэтому вызов RegisterNatives будет терпеть неудачу во время выполнения.

Стоит отметить, что все функции попадают в динамическую таблицу по умолчанию, поэтому любой может легко декомпилировать ваш собственный код. Каждый вызов функции встроен в двоичный файл на случай, если JNI должен его найти. Это можно изменить с помощью опции компилятора -fvisibility. Я бы порекомендовал, чтобы все устанавливали это на -fvisibility=hidden, чтобы защитить ваш код, а затем используйте JNIEXPORT, чтобы функции флага имели внешнюю видимость.

Использование команды strip просто удаляет символы отладки, динамическая таблица является отдельной. Имейте игру с objdump, чтобы увидеть, сколько человек может выбраться из ваших .so файлов.

Недавно мы с этим справились, надеемся, что это поможет кому-то.

EDIT: мы используем настраиваемую систему сборки, поэтому параметр видимости может быть установлен по умолчанию для других установок сборки. Дополнительная информация доступна в этом SO-ответе.

Ответ 2

Вы можете найти определение этих макросов в зависимой от машины части вашего JNI (обычно в $JAVA_HOME/include/<arch>/jni-md.h).

Короче говоря, JNIEXPORT содержит любые директивы компилятора, необходимые для обеспечения правильной экспорта данной функции. В андроиде (и других системах на базе Linux) это будет пустым.

JNICALL содержит любые директивы компилятора, необходимые для обеспечения того, чтобы данная функция обрабатывалась надлежащим соглашением о вызовах. Вероятно, на андроиде тоже пусто (это __stdcall на w32).

В общем, вы должны оставить их, даже если они пустые #define s.

Ответ 3

Просто запустите "javah" на ваших родных классах и используйте все, что он генерирует. Вам не нужно знать все это, когда у вас есть инструмент, который может обеспечить его со 100% -ной надежностью.

Ответ 4

Простыми словами:

  • JNIEXPORT, если вы должны использовать семейство функций registerNatives, тогда вы не должны использовать JNIEXPORT. В противном случае вы ДОЛЖНЫ использовать его.
  • JNICALL должен использоваться ВСЕГДА.

JNIEXPORT гарантирует, что функция отображается в таблице символов. JNICALL гарантирует, что функция использует правильное соглашение о вызове. На Android JNICALL имеет другое значение, основанное на архитектуре. ARM пуст, который может заставить вас не включать его. Но вы должны использовать JNICALL.

registerNatives позволяет связать функцию с программным путем на JNI_onLoad или в будущем. registerNatives позволяет раньше поймать неправильные имена функций, и я рекомендую этот маршрут.