C++调用Java方法

最近在搞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 L
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

猜你喜欢

转载自blog.csdn.net/toyauko/article/details/83109325
今日推荐