Вы не можете кэшировать значение JNIEnv.
За Глава 5: API-интерфейс Invisionation спецификации JNI (шахта для разметки):
Присоединение к ВМ
Указатель интерфейса JNI (JNIEnv) действителен только в текущем
нить . Если другой поток должен получить доступ к Java VM, он должен
первый вызов AttachCurrentThread()
, чтобы присоединиться к виртуальной машине и
получить указатель на интерфейс JNI. После подключения к ВМ, родной
поток работает так же, как обычный поток Java, работающий внутри собственного
метод. Собственный поток остается подключенным к виртуальной машине, пока он не вызывает
DetachCurrentThread()
чтобы отделить себя.
Для идентификатора метода и объекта обратного вызова необходимо создать глобальные ссылки:
gbl_method = jenv->NewGlobalReference( mid );
gbl_callback = jenv->NewGlobalReverend( jarg2 );
Это создает одну серьезную проблему - ссылка на объект, хранящаяся в gbl_callback
, приведет к тому, что объект Java никогда не будет собирать мусор.
Чтобы вернуть функцию обратного вызова C ++, вам сначала нужно преобразовать указатель функции в допустимый тип Java. Строгое соответствие стандарту C (JNI - это на самом деле C, а не C ++, поэтому передача объектов туда-сюда становится немного мутной, когда вы смешиваете в C ++), что означает, что вы не можете трактовать указатель функции как что-либо иное, чем последовательность байтов, что означает, что вам нужно преобразовать указатель функции в байтовый массив Java и вернуть его в Java. Затем преобразуйте этот байтовый массив Java обратно в указатель на функцию, если вы хотите использовать его в собственном коде. Это много кода, и в JNI, чем больше кода вы пишете, тем больше вероятность, что вы что-то сломаете - JNI является хрупким .
Но как в Windows, так и в POSIX указатель функции вписывается в jlong
, а jlong
может точно представлять значение указателя функции, так что это хак, который работает (а в POSIX это, вероятно, даже не хак , но я не собираюсь пытаться найти фактические спецификации указателя функции POSIX, которые позволили бы это приведение).
Самый простой способ сделать это - создать поле long
в вашем Java-объекте и вернуть указатель функции, приведенный к jlong
:
JNIEXPORT jlong JNICALL Java_apiJNI_Info_1t_1callbackTest_1set(
JNIEnv *jenv, jclass jcls, jlong jarg1, jobject jarg1_, jobject jarg2)
{
...
return( ( jlong ) WrapperFunction );
}
Это можно назвать примерно так:
// Info_t has a long field called callback
Info_t info_t = new Info_t();
info_t.callback = info_t.setCallbackTest(new CallbackTest() {
@Override
public void onCallback(int num) {
System.out.println("callback: " + num);
}
});
По моему опыту, чем меньше звонков JNI вы делаете, тем лучше. Поэтому, если вы можете передать значение через параметр или вернуть его из вызова функции, это проще, безопаснее и намного надежнее, чем пытаться получить / установить значения поля объекта с помощью вызовов JNI.
Но тогда вам нужно будет передать значение обратного вызова в качестве другого параметра:
// function pointer, returns void, takes int arg
typedef void (*funcPtr)( int );
JNIEXPORT jobject JNICALL Java_apiJNI_Info_1t_1callbackTest_1get(
JNIEnv *jenv, jclass jcls, jlong callback, jlong jarg1, jobject jarg1_)
{
funcPtr f = ( funcPtr ) callback;
f( 4 );
...
}