Вернуть указатель на функцию из поведения C ++ в объект Java с помощью JNI - PullRequest
0 голосов
/ 14 ноября 2018

Это у меня над головой, так что мы идем.

Я устанавливаю указатель функции в C ++ с интерфейсным Java-объектом, который выглядит так же, как на стороне Java:

    Info_t info_t = new Info_t();
    info_t.setCallbackTest(new CallbackTest() {
        @Override
        public void onCallback(int num) {
            System.out.println("callback: " + num);
        }
    });

, где CallbackTest() - интерфейс только с одним методом:

public interface CallbackTest {
    void onCallback(int num);
}

и setCallbackTest() относятся к нативному методу Java, который отражается на стороне C ++ следующим образом:

JNIEnv *gbl_env;
jmethodID gbl_method;
jobject gbl_callback;

void WrapperFunction(int a) {
    gbl_env->ExceptionClear();
    gbl_env->CallVoidMethod(gbl_callback, gbl_method, a);
    if(gbl_env->ExceptionOccurred()) {
        gbl_env->ExceptionClear();
    }
    return;
}

JNIEXPORT void JNICALL Java_apiJNI_Info_1t_1callbackTest_1set(JNIEnv *jenv, jclass jcls, jlong jarg1, jobject jarg1_, jobject jarg2) {
    Info *arg1 = (Info *) 0;

    (void) jenv;
    (void) jcls;
    (void) jarg1_;
    arg1 = *(Info **)&jarg1;
    {
        jclass clazz;
        jmethodID mid;

        clazz = jenv->GetObjectClass(jarg2);
        mid = jenv->GetMethodID(clazz, "onCallback", "(I)V");
        if(mid == 0) {
            std::cout << "could not get method id" << std::endl;
            return;
        }
        gbl_env = jenv;
        gbl_method = mid;
        gbl_callback = jarg2;
    }
    // here I am setting function pointer to the helper method which invokes the java code
    if(arg1) (arg1)->completionCB = WrapperFunction;
}

Все это было написано на основе Реализации функции обратного вызова в JNI с использованием интерфейса

Я попытался вызвать метод onCallback(), определенный в Java на стороне C ++, и он работает (как в ссылке выше), но сейчас я пытаюсь установить значение для такого struct в C ++

typedef void (*callbackFunction)(int);
typedef struct Info
{
    callbackFunction    completionCB;
    void                *caller;
} Info_t;

поэтому позже в какой-то момент я хотел бы вернуть callbackFunction completionCB обратно в Java как объект и, вероятно, делать с ним разные вещи.

Мой вопрос: как я могу вернуть / отобразить поведение указателя функции из C ++ обратно в объект Java? В принципе, я хотел бы сделать то же самое, но в обратном порядке. Теперь я могу отобразить интерфейс Java на указатель на функцию. Теперь я хочу отобразить поведение указателя функции на объект Java.

РЕДАКТИРОВАТЬ: До сих пор я придумал такой геттер для него, но я не уверен, как действовать

JNIEXPORT jobject JNICALL Java_apiJNI_Info_1t_1callbackTest_1get(JNIEnv *jenv, jclass jcls, jlong jarg1, jobject jarg1_) {
    jobject jresult = 0;
    Info *arg1 = (Info *) 0;
    callbackFunction result;

    (void)jenv;
    (void)jcls;
    (void)jarg1_;
    arg1 = *(Info **)&jarg1;
    result = ((arg1)->completionCB);

    {
        jclass clazz = jenv->FindClass("com/CallbackTest");
        ???
    }
    ???
    return ;
}

1 Ответ

0 голосов
/ 14 ноября 2018

Вы не можете кэшировать значение 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 );

    ...
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...