您的位置:首页 > 编程语言 > C语言/C++

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

2018-01-15 19:06 603 查看
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封装了线程锁上锁和解锁,保证上锁后一定会解锁。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: