Когда использовать 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
позволяет раньше поймать неправильные имена функций, и я рекомендую этот маршрут.