使用JNI从C++调用Java方法

4
我希望您能为一个特定的USB设备编写C++库并将其转换为JAVA语言。 该库支持回调函数,以通知应用程序有关USB设备连接和断开连接的信息。
回调函数必须具有以下特定格式:
DWORD callbackFunction(void *params);

所以我在JNI dll中实现了这样一个函数,并希望每当调用该函数时,调用Java包装器中的一个函数。

问题是我应该使用哪个JNIENV来调用GetObjectClass、GetMethodID和CallVoidMethod呢?


这是我初始化DLL的方式。"Set(AttachDetach)Callback"方法接受回调函数(第一个参数)和void*参数(第二个参数),在检测到模块附加/分离时将传递给该函数。
JNIEXPORT void JNICALL Java_MyPackage_MyClass_InitializeDLL
(JNIEnv *env, jobject obj, jobject callback)
{
      // Storing callback object in global variable.
    callBackObj = callback;

    env->GetJavaVM(&jvm);

    MyInstance = new MyClass();
    MyInstance ->SetAttachCallback(AttachCallBack, &callBackObj);
    MyInstance ->SetDetachCallback(DetachCallBack, &callBackObj);

      // Testing!
    jclass callBackCls = env->FindClass("MyPackage/MyClassCallBacks");
    jmethodID mid = env->GetMethodID(callBackCls, "attach", "(B)V");
    if (!mid)
        return ; /* method not found */
      //This call here works well
    env->CallVoidMethod(callBackObj, mid, 5);
}

然后我在USB设备的DLL中设置了一个回调函数,在我连接设备时它成功地被调用。

我放置在USB设备附加回调中的代码如下:

DWORD CALLBACK AttachCallBack(CallbackParams* params)
{
    JNIEnv *env;
    jvm->AttachCurrentThread((void **)&env, NULL);

    jclass callBackCls = env->FindClass("MyPackage/MyClassCallBacks");
    jmethodID mid = env->GetMethodID(callBackCls, "attach", "(B)V");
    if (!mid)
        return -1; /* method not found */
      // This call fails with an access violation Exception
    env->CallVoidMethod(*((jobject *)(params->param)), mid, params->moduleIndex);
      // This fails the same way too
    env->CallVoidMethod(callBackObj, mid, 5);

    jvm->DetachCurrentThread();

    return 0;
}

在使用AttachCurrentThread之前,我根本无法使用JNIENV指针。但现在除了调用CallVoidMethod之外,对该指针的任何其他使用都是成功的。您看到这里有什么问题吗?

让我补充一下,MyPackage.MyClassCallBacks是一个接口,它的方法在另一个类中实现,即“callBackClass”。

4个回答

6

你需要引用当前JVM:

JavaVM *jvm;

您可以在 C++ 后端添加一个初始化方法,当程序启动时,该方法会获取此引用:
JNIEXPORT void JNICALL init(JNIEnv *env, jclass){
    env->GetJavaVM(&jvm);
}

当观察USB连接/断开时,您可以通过以下方式从此JavaVM获取JNIEnv:

JNIEnv *env;
jvm->AttachCurrentThread((void **)&env, NULL);

//your code here

jvm->DetachCurrentThread();

这是为了每次USB设备改变时创建一个新的线程而实现的。如果您仅使用一个线程进行检查,则只需要在初始化程序中附加一次(也许?)然后您将拥有有效的JNIEnv,只要您的本机线程附加到JVM


你好Jakub,谢谢你的回复。你解决了我的问题。但是现在我又遇到了一个新问题。我可以使用从GetJavaVM方法中获取的JNIENV参数获取jclass和method ID,但在将其传递给JAVA函数(使用CallVoidMethod)时,会出现访问违规异常。在本地库中设置的用于usb设备的回调函数在与主线程不同的新线程中被调用。 - Taheri
通常,您想要在Java中使用的每个新线程都必须使用AttachCurrentThread()进行附加。这将使JVM将此本地线程与Thread对象相关联。如果您尝试从未附加的线程调用Java函数,则会出现访问冲突。请发布更多代码,我们来看看。 - Jakub Zaverka

2

您可能需要在C语言中创建一个队列并等待它,或使用Java线程轮询它。这将始终有一个当前的JNIEnv可用。


似乎您不能保存上一个JNI调用的JNIENV并重复使用它。

您的回调似乎返回了参数,这些参数可能是您设置回调时传递的。您可以将其中一个参数作为JNIENV。


通常情况下,您不应该保存JNIEnv以供以后重复使用,即使偶尔这样做可能会起作用。Java回调应该从JVM引用开始,从中获取适当的JNIEnv指针。 - technomage
你能解释一下不保存在某个地方的情况下如何完成这个操作吗? - Peter Lawrey
第一次进入JNI领域时,您需要保存对VM的引用(请参见Jakub的答案)。通过VM引用,您可以稍后检索特定于上下文的JNIEnv指针。 - technomage
1
是的,保存JNI并稍后使用会导致访问冲突异常。 - Taheri

1

I had the same problem too. It was as though the reference to the object, created in the initialization method, was of no use in the other methods. And it is indeed like that.
The solution is in the initialization of the reference to the object, which must be initialized not simply with

callBackObj = callback

but with

callbackObj = env->NewGlobalRef(callback)

Same issue here: Objective C calling Java methods using JNI


谢谢你的评论。我不得不把那个问题留下并开始另一个任务。但还是感谢你的回复,它似乎很相关。我会检查它(早晚)。顺便问一下,你编辑了你的帖子吗?通知好像不同了! - Taheri
欢迎您,顺便说一下,很抱歉回复晚了。 我编辑了我的帖子吗?嗯...可能是的。那是我最初的回答之一,我在响应格式方面遇到了一些问题。此外,fbfcn纠正了我的语法(我也感谢他)。 - kekolab

0
创建一个JNI init(JNIEnv * env, jclass c (或 jobject o))。
save param #1 JNIEnv

save param #2 jclass (if static) 
   or 
save param #2 jobject (in non-static)

lookup and save the jmethodID(s) for the Java method(s) you will be invoking.  

同时拥有一个JNI shutdown(JNIEnv * env, jclass (或 jobject)) 以进行本地关闭/清理是个好主意


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接