最近在搞JNI那块,发现网上很多都是Java调用JNI,然后再调用C++的方法。而当C++函数里调用Java的方法,网上的文章可以说是少之又少,所以写此篇文章共勉。。。。
本文介绍两种方法,一是C++主动调用Java的情况;另一种是Java调用了C++,然后在该调用的C++里又回调另外的一个Java方法。其实这两种方法(或其他方法),都是要用到 JNIEnv,有关JNI的讲解可查阅此文章https://blog.csdn.net/tom_221x/article/details/69215286 在此感谢原文作者。
首先讲解本文介绍的第二种方法:Java调用C++,然后C++再回调另一个Java方法,直接上代码吧:
JavaVM* jvm = NULL;
jclass myClass = NULL;
jclass global_class = NULL;
jmethodID mid_method;
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm,void* reserved){
if (vm == NULL)
{
return JNI_ERR;
}
JNIEnv *env = NULL;
jvm = vm;
if(vm->GetEnv((void**)&env,JNI_VERSION_1_4)!=JNI_OK){
return JNI_ERR;
}
myClass = (env)->FindClass("com/shizhuangyuan/mycpp/MainActivity");
global_class = (jclass)env->NewGlobalRef(myClass);
mid_method = (env)->GetMethodID(global_class,"cpp2JavaTest","(I)V");
return JNI_VERSION_1_4;
}
这里讲解下JNI_OnLoad这个函数:
当Android的VM(Virtual Machine)执行到C组件(即*so档)里的System.loadLibrary()函数时,首先会去执行C组件里JNI_OnLoad()函数。
它的用途有二:
1、告诉VM此C组件使用那一个JNI版本。
如果你的*.so档没有提供JNI_OnLoad()函数,VM会默认该*.so档是使用最老的JNI 1.1版本。
由于新版的JNI做了许多扩充,如果需要使用JNI的新版功能,
例如JNI 1.4的java.nio.ByteBuffer,就必须藉由JNI_OnLoad()函数来告知VM。
2、由于VM执行到System.loadLibrary()函数时,就会立即先呼叫JNI_OnLoad(),
所以C组件的开发者可以藉由JNI_OnLoad()来进行C组件内的初期值之设定(Initialization) 。
在JNI_OnLoad函数里,首先通过FindClass把Java里的类找出来,接着GetMethodID找到需要调用的Java方法。第二个参数是该Java方法名。这里解析下第三个参数"(I)V"的含义:表示形参为int型,返回值为Void型的一个Java方法,详细的类型符号对照表如下所示:
Java类型 对应签名符号
Integer I
Short S
Char C
Long J
Float F
Double D
Byte B
Boolean Z
Void V
数组 [内部类型
Object对象 L开头,包名/类名,”;”结尾,$标识嵌套类
下面举几个例子吧:
void javaDemo1(int a, int b) //(II)V
double javaDemo2(string a, int b) //(Ljava/lang/String;I)D
void javaDemo3() //()V
string javaDemo4(string[] a, boolean b) //([java/lang/String;Z)Ljava/lang/String;
当虚拟机释放该C库时,则会调用JNI_OnUnload()函数来进行善后清除动作。代码如下:
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
if (jvm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
return;
}
env->DeleteGlobalRef(global_class);
return;
}
接着便是C++调用Java方法的核心代码了:
void cpp2jni(int msg){
JNIEnv *env = NULL;
if (jvm->AttachCurrentThread(&env, NULL))将当前线程注册到虚拟机中
{
return;
}
//实例化该类
jobject jobject = env->AllocObject(global_class);//分配新 Java 对象而不调用该对象的任何构造函数。返回该对象的引用。
//调用Java方法
(env)->CallVoidMethod(jobject, mid_method,msg);
jvm->DetachCurrentThread();
}
开开心运行代码,但是会出现一个运行时错误:
Thread[1,tid=9643,Native,Thread*=0x7f7ec96a00,peer=0x74e4ea30,"main"] attempting to detach while still running code
这是因为本例子是直接通过Java调用JNI里的函数,然后在JNI里调用C++函数,最后在该C++函数里调Java,即Java---JNI---C++---Java。
上述出现错误的原因是DetachCurrentThread()时报的错,调用DetachCurrentThread函数的地方在java线程中,即在java调用C++代码时在C++代码中调用了AttachCurrentThread方法来获取JNIEnv,此时JNIEnv已经通过参数传递进来,你不需要再次AttachCurrentThread来获取。在释放时就会报错。
所以上述方便适用于:C++直接调用Java方法。
但如果通过Java调用了C++,接着直接利用C++回调Java方法,代码可以修改成这样:
void cpp2jni(int msg){
JNIEnv *env = NULL;
int status;
bool isAttached = false;
status = jvm->GetEnv((void**)&env, JNI_VERSION_1_4);
if (status < 0) {
if (jvm->AttachCurrentThread(&env, NULL))将当前线程注册到虚拟机中
{
return;
}
isAttached = true;
}
//实例化该类
jobject jobject = env->AllocObject(global_class);//分配新 Java 对象而不调用该对象的任何构造函数。返回该对象的引用。
//调用Java方法
(env)->CallVoidMethod(jobject, mid_method,msg);
if (isAttached) {
jvm->DetachCurrentThread();
}
}
其实就是在第一种方法加些判断就可以了。
本文最后,直接附上本项目的Demo代码吧:
github地址:https://github.com/Liangzhuhua/MyCPP.git
CSDN地址:https://download.csdn.net/download/toyauko/10726905
————————————————
版权声明:本文为CSDN博主「雪飘碧鸳」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/toyauko/article/details/83109325
免责声明: | |
1、 | 资源售价只是赞助,不代表代码或者素材本身价格。收取费用仅维持本站的日常运营所需。 |
2、 | 本站资源来自用户上传,仅供用户学习使用,不得用于商业或者非法用途,违反国家法律一切后果用户自负。用于商业用途,请购买正版授权合法使用。 |
3、 | 本站资源不保证其完整性和安全性,下载后自行检测安全,在使用过程中出现的任何问题均与本站无关,本站不承担任何技术及版权问题,不对任何资源负法律责任。 |
4、 | 如有损害你的权益,请联系275551777@qq.com及时删除。 |