当前位置: 首页 > 知识库问答 >
问题:

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

蒋茂
2023-03-14

这已经超出我的想象了,我们开始吧。

我正在用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中使用接口实现回调函数编写的

我已经尝试在C端调用Java中定义的onCallback()方法,它可以工作(就像上面的链接一样),但是现在我正在尝试在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个答案

齐思淼
2023-03-14

不能缓存JNIEnv值。

根据第5章:JNI规范的调用API(粗体):

附加到VM

JNI接口指针(JNIEnv)仅在当前线程中有效。如果另一个线程需要访问JavaVM,它必须首先调用attachMONtThread()来将自己附加到VM并获得JNI接口指针。一旦附加到VM,本地线程的工作方式就像在本地方法中运行的普通Java线程一样。本地线程保持附加到VM,直到它调用DetachMONtThread()来分离自己。

对于方法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是脆弱的。

但是在视窗和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 );

    ...
}
 类似资料:
  • C++ 指针 在上一章中,我们已经了解了 C++ 中如何从函数返回数组,类似地,C++ 允许您从函数返回指针。为了做到这点,您必须声明一个返回指针的函数,如下所示: int * myFunction() { . . . } 另外,C++ 不支持在函数外返回局部变量的地址,除非定义局部变量为 static 变量。 现在,让我们来看下面的函数,它会生成 10 个随机数,并使用表示指针的数组名(即第

  • 问题内容: 我发现了大量有关如何在JNI中生成2D基本数组并将其返回给Java的文档。但是这些信息无法描述如何在 C中* 为上下文传递 已存在的 2D浮点数组(float **)。 * 为了明确描述我的问题,我将添加一些我想实现的 C 伪代码: 考虑到我无法找到任何描述这种情况的信息,我认为这不是直截了当的。 感谢您提供任何有用的信息。 问题答案: 感谢Timo的帮助和链接。为了后代,我添加了一个

  • C语言允许函数的返回值是一个 指针(地址),我们将这样的函数称为 指针函数。下面的例子定义了一个函数 strlong(),用来返回两个字符串中较长的一个: 运行结果: C Language↙ c.biancheng.net↙ Longer string: c.biancheng.net 用指针作为函数返回值时需要注意的一点是,函数运行结束后会销毁在它内部定义的所有局部数据,包括局部变量、局部数组和

  • 问题内容: 我正在尝试使用android NDK。 有没有办法将在JNI中创建的数组(以我的情况为例)返回给Java?如果是这样,请提供一个可以执行此操作的JNI函数的简单示例。 问题答案: 如果你已经阅读了文档,但仍然有一些问题应作为最初问题的一部分。在这种情况下,示例中的JNI函数将创建多个数组。外部数组由使用JNI函数创建的“对象”数组组成。从JNI的角度来看,这就是一个二维数组,即一个包含

  • 问题内容: 我试图弄清楚需要更改什么SWIG接口文件才能处理getFoo返回一个指向自定义结构数组(sender_id_t)的指针。没有任何特殊的SWIG接口代码,我只得到Java端的指针。如何将指针转换为可以循环或迭代的内容(在Java中),以便获取每个sender_id_t id值?感谢任何建议。 C结构: C函数: 例外: 问题答案: 最简单的解决方案不涉及编写任何JNI,实际上是方法2。因

  • 我在玩C语言中的函数指针只是为了学习。我尝试调用一个空函数,并将其结果设置为int。 我从中得到的结果是: 在对它进行了一点研究之后,我意识到main正在函数()中打印printf语句中的字符数。为什么会这样?这是预期产出吗?