使用JNI将函数指针从C ++行为返回到Java对象

时间:2018-11-14 13:59:10

标签: java c++ java-native-interface function-pointers swig

这是让我头疼的方法,所以我们开始吧。

我正在用接口Java对象在C ++中设置一个函数指针,该对象看起来像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;
}

所有这些都是根据Implement callback function in JNI using Interface

编写的

我尝试在C ++端调用Java中定义的onCallback()方法,并且该方法可以正常工作(就像上面的链接一样),但是现在我试图在C ++下设置此类struct的值

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 :(得分:1)

您不能缓存JNIEnv值。

根据JNI规范的Chapter 5: The Invocation API(正在讨论我的问题):

  

附加到VM

     

JNI接口指针(JNIEnv)仅在当前   线程。如果另一个线程需要访问Java VM,则必须   首先调用AttachCurrentThread()将其自身附加到VM,然后   获取一个JNI接口指针。附加到VM后,本地   线程的工作方式就像在本机内部运行的普通Java线程一样   方法。本机线程保持连接到VM,直到它调用   DetachCurrentThread()以脱离自身。

对于方法ID和回调对象,您需要创建一个全局引用:

    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函数指针规范。

最简单的方法是在Java对象中创建一个long字段,然后返回将函数指针强制转换为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 );

    ...
}