JNI编程——Java与c++代码互相调用及数据传递

Java层作为应用层,需要启动一个c++服务,同时需要互相调用及数据交互。

Java调用c++,并传递int型参数

JNIEXPORT void JNICALL Java_com_lp_lcmedia_LCInterface_initJni(JNIEnv *env, jobject instance, jint mode) {}
Java调用c++,并传递int型参数

JNIEXPORT jboolean JNICALL Java_com_lp_lcmedia_LCInterface_sendLiveFrame(JNIEnv *env, jobject instance, jbyteArray frame, jint offset, jint length, jboolean i_frame, jlong handle) {
    jboolean is_copy = false;
    // GetByteArrayElements 第二个参数设为false,表示不复制数据,直接引用
    jbyte* p_frame = env->GetByteArrayElements(frame, &is_copy);
    // 得到p_frame后在这里使用
    bool r = device_media::s_live_data_callback(0, (uint8_t*) p_frame + offset, length, handle);
    // 使用完以后必须显式释放
    env->ReleaseByteArrayElements(frame, p_frame, 0);
    return r;
}

c++调用Java的非static方法
需要有Java层的实例才能调用。在上一个例子中Java调用c++函数时创建一个Java实例,并通过全局/静态变量保存下来:

static jobject s_obj = nullptr;
static JavaVM *g_jvm = nullptr;
JNIEXPORT void JNICALL Java_com_lp_lcmedia_LCInterface_initJni(JNIEnv *env, jobject instance, jint mode) {
    if (g_jvm == nullptr)
        env->GetJavaVM(&g_jvm);
    if (s_obj == nullptr)
        s_obj = env->NewGlobalRef(instance);
}
之后通过这个创建的实例来调用Java层的非static方法。与创建对应地,当不再需要调用Java层方法时,必须手动调用DeleteGlobalRef来释放这个实例,可以在c++线程做,也可以由Java层来做:

JNIEXPORT void JNICALL Java_com_lp_lcmedia_LCInterface_destroyJni(JNIEnv *env, jobject instance) {
    if (s_obj) {
        env->DeleteGlobalRef(s_obj);
        s_obj = nullptr;
        g_jvm = nullptr;
    }
}
接下来是c++层调用Java层非static的方法的例子。

a. 比如Java层LCInterface类有个无参无返回值回调方法:

public class LCInterface {
    private void onDestroy() {}
}

函数签名为"()V",然后由c++层来调用:

void notify_event() {
    if (!g_jvm || !s_obj) {
        LOGE("%s: jni error: g_jvm or s_obj is null", __func__);
        return;
    }
    JNIEnv *env;
	
    // 调用Java方法必须让当前c++的线程>Attach在JVM的线程上,从而获得Java环境,并在调用完后Detach
#ifdef _LIBCPP_COMPILER_CLANG
    g_jvm->AttachCurrentThread(&env, nullptr);
#else
    g_jvm->AttachCurrentThread((void**) &env, nullptr);
#endif

    do {
        jclass cls = env->GetObjectClass(s_obj);	// 获取LCInterface类
        if (cls == nullptr) {
            LOGE("%s: jni error: GetObjectClass fail", __func__);
            break;
        }       
        jmethodID mid = env->GetMethodID(cls, "onDestroy", "()V");	// 获取方法名
        if (mid == nullptr) {
            LOGE("%s: jni error: GetMethodID onDestroy fail", __func__);
            break;
        }
        env->CallVoidMethod(s_obj, mid);	// 调用onDestroy()
    } while (false);

    if (g_jvm->DetachCurrentThread() != JNI_OK) {
        LOGE("%s: DetachCurrentThread fail", __func__);
    }
}
b. 传递int型参数,并返回int/String型返回值

Java层方法:

private int setIntProperty(int key) {return 0;}
private int setStringProperty(int key) {return "hello";}
签名分别为:"(I)I"和"(I)Ljava/lang/String;"。jni层调用代码:
void jni() {
    if (!g_jvm || !s_obj) {
        LOGE("%s: jni error: g_jvm or s_obj is null", __func__);
        return;
    }
    JNIEnv *env;
    jclass cls;
    jmethodID mid;
    
#ifdef _LIBCPP_COMPILER_CLANG
    g_jvm->AttachCurrentThread(&env, nullptr);
#else
    g_jvm->AttachCurrentThread((void**) &env, nullptr);
#endif
    do {
        jclass cls = env->GetObjectClass(s_obj);
        if (cls == nullptr) {
            LOGE("%s: jni error: GetObjectClass fail", __func__);
            break;
        }
        jmethodID mid = env->GetMethodID(cls, "setIntProperty", "(I)I");
        if (mid == nullptr) {
            LOGE("%s: jni error: GetMethodID setIntProperty fail", __func__);
            break;
        }
        int value = env->CallIntMethod(s_obj, mid, key); // 得到Java方法的int型返回值
		
        jclass cls2 = env->GetObjectClass(s_obj);
        if (cls2 == nullptr) {
            LOGE("%s: jni error: GetObjectClass fail", __func__);
            break;
        }
        jmethodID mid2 = env->GetMethodID(cls2, "setStrProperty", "(I)Ljava/lang/String;");
        if (mid2 == nullptr) {
            LOGE("%s: jni error: GetMethodID setStrProperty fail", __func__);
            break;
        }
        jstring jstr = (jstring) env->CallObjectMethod(s_obj, mid2, key);
		std::string str(env->GetStringUTFChars(str, nullptr)); // 获取到Java层返回的字符串地址并复制到std::string
		env->ReleaseStringUTFChars(str, nullptr);	// 使用完显式释放
		
    } while (false);

    if (g_jvm->DetachCurrentThread() != JNI_OK) {
        LOGE("%s: DetachCurrentThread fail", __func__);
    }
}

需要注意的是,如果传递的long型变量,如果是用ndk编译,那么需要将变量转为long long型,否则会导致传递数据错误。

c. 返回ByteBuffer对象。先获取到ByteBuffer对象,再逐个获取ByteBuffer的关键属性,如array(),position(),remaining()。Java层方法:

private ByteBuffer returnFrame_callback() {return ByteBuffer.allocate(100);}
签名:"()Ljava/nio/ByteBuffer;"。jni层调用代码:

void jni() {
    if (!g_jvm || !s_obj) {
        LOGE("%s: jni error: g_jvm or s_obj is null", __func__);
        return;
    }
    JNIEnv *env;
#ifdef _LIBCPP_COMPILER_CLANG
    g_jvm->AttachCurrentThread(&env, nullptr);
#else
    g_jvm->AttachCurrentThread((void**) &env, nullptr);
#endif
    do {
        jclass cls = env->GetObjectClass(s_obj);
        if (cls == nullptr) {
            LOGE("%s: jni error: GetObjectClass fail", __func__);
            break;
        }
        jmethodID mid = env->GetMethodID(cls, "returnFrame_callback", "()Ljava/nio/ByteBuffer;");
        if (mid == nullptr) {
            LOGE("%s: jni error: GetMethodID returnFrame_callback fail", __func__);
            break;
        }
        jobject obj_bytebuf = env->CallObjectMethod(s_obj, mid); // 获取到Java层返回的ByteBuffer对象
        if (!obj_bytebuf) {
            break;
        }
        jclass cls_bytebuf = env->GetObjectClass(obj_bytebuf);
        jmethodID mtd_pos = env->GetMethodID(cls_bytebuf, "position", "()I");
        jint position = env->CallIntMethod(obj_bytebuf, mtd_pos);
        jmethodID mtd_remaining = env->GetMethodID(cls_bytebuf, "remaining", "()I");
        jint remaining = env->CallIntMethod(obj_bytebuf, mtd_remaining);
        jmethodID mtd_array = env->GetMethodID(cls_bytebuf, "array", "()[B");
        jbyteArray array_ = (jbyteArray) env->CallObjectMethod(obj_bytebuf, mtd_array);	// 获取到ByteBuffer对象存储的数据的地址
        jbyte *array = env->GetByteArrayElements(array_, nullptr); // 转为本地能访问的字符地址,内部实现为增加数据空间的引用
	// 在这里将ByteBuffer对象的数据复制到本地
	env->ReleaseByteArrayElements(array_, array, 0); // 释放数据空间,内部实现为减少数据空间的引用
    } while (false);
    if (g_jvm->DetachCurrentThread() != JNI_OK) {
        LOGE("%s: DetachCurrentThread fail", __func__);
    }
}
d. 传入字符串
Java层方法:
private void audioArrivd_callback(byte[] pBuffer, int dwBufSize) {}

签名:"([BI)V"。jni层调用代码:

void jni() {
    if (!g_jvm || !s_obj)
        return;

    JNIEnv *env;
#ifdef _LIBCPP_COMPILER_CLANG
    g_jvm->AttachCurrentThread(&env, nullptr);
#else
    g_jvm->AttachCurrentThread((void**) &env, nullptr);
#endif

    do {
        jclass cls = env->GetObjectClass(s_obj);
        if (cls == nullptr) {
            LOGE("%s: jni error: GetObjectClass fail", __func__);
            break;
        }
        jmethodID mid = env->GetMethodID(cls, "audioArrivd_callback", "([BI)V");
        if (mid == nullptr) {
            LOGE("%s: jni error: GetMethodID audioArrivd_callback fail", __func__);
            break;
        }

        size_t dwBufSize = 1;
		int8_t pBuffer[10] = {0};
        jbyteArray a_audio = env->NewByteArray(dwBufSize);
        env->SetByteArrayRegion(a_audio, 0, dwBufSize, (int8_t*) pBuffer);
        env->CallVoidMethod(s_obj, mid, a_audio, dwBufSize);
    } while (false);
    if (g_jvm->DetachCurrentThread() != JNI_OK) {
        LOGE("%s: DetachCurrentThread fail", __func__);
    }
}

Attach/Detach JVM线程可能存在风险,比如在notify_event函数中,在AttachCurrentThread后,如果因为疏忽,代码出现异常没有捕获导致没有运行到最后执行DetachCurrentThread,会造成程序直接崩溃,而且这样的写法造成代码冗余,不优雅。针对这个问题,我利用RAII技术使用一个类来封装Attach/Detach JVM线程的操作,即分别在构造和析构函数中Attach和Detach JVM线程:

class jvm_thread {
public:
    typedef JavaVM *jvm_t;
    typedef JNIEnv *env_t; 
    jvm_thread(JavaVM* jvm) {
        if (!jvm) throw std::runtime_error("jvm_thread::" + std::string(__func__) + ": jvm is null");
        jint rt;
        if ((rt = g_jvm->AttachCurrentThread(
#ifdef _LIBCPP_COMPILER_CLANG
                &m_env,
#else
                (void**) &m_env,
#endif
                nullptr)) != JNI_OK) {
            std::stringstream ss;
            ss << "jvm_thread::" << __func__ << " - AttachCurrentThread: " << rt;
            throw std::runtime_error(ss.str());
        }
    }
    virtual ~jvm_thread() {
        jint rt;
        if ((rt = m_jvm->DetachCurrentThread()) != JNI_OK)
            LOGE("jvm_thread::%s: DetachCurrentThread return %d", __func__, rt);
    }
    const env_t& env() const {return m_env;}
private:
    jvm_thread(const jvm_thread& orig);
    env_t m_env;
    const jvm_t m_jvm;
};
这样只要函数内定义一个jvm_thread就安全的实现了Attach/Detach JVM线程的操作。由于 jvm_thread构造函数内可能会抛出异常,因此需要加个捕获:

void notify_event() {
    if (!s_obj) {
        LOGE("%s: jni error: s_obj is null", __func__);
        return;
    }
    try {
        jvm_thread jvmthread(g_jvm);
        do {
            jclass cls = jvmthread.env()->GetObjectClass(s_obj);
            if (cls == nullptr) {
                LOGE("%s: jni error: GetObjectClass fail", __func__);
                break;
            }
            jmethodID mid = jvmthread.env()->GetMethodID(cls, "onDestroy", "()V");
            if (mid == nullptr) {
                LOGE("%s: jni error: GetMethodID onDestroy fail", __func__);
                break;
            }
            jvmthread.env()->CallVoidMethod(s_obj, mid, time);
        } while (false);
    } catch (std::exception& e) {
        LOGE("exception in %s: %s", __func__, e.what());
    }
}
这样的思路在std::unique_lock中已经出现,std::unique_lock也利用RAII封装了线程锁上锁和解锁,保证上锁后一定会解锁。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值