JNI编程——Java与c++代码互相调用及数据传递
2018-01-15 19:06
603 查看
Java层作为应用层,需要启动一个c++服务,同时需要互相调用及数据交互。
Java调用c++,并传递int型参数
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实例,并通过全局/静态变量保存下来:
之后通过这个创建的实例来调用Java层的非static方法。与创建对应地,当不再需要调用Java层方法时,必须手动调用DeleteGlobalRef来释放这个实例,可以在c++线程做,也可以由Java层来做:
接下来是c++层调用Java层非static的方法的例子。
a. 比如Java层LCInterface类有个无参无返回值回调方法:
函数签名为"()V",然后由c++层来调用:
b. 传递int型参数,并返回int/String型返回值
Java层方法:
签名分别为:"(I)I"和"(I)Ljava/lang/String;"。jni层调用代码:
需要注意的是,如果传递的long型变量,如果是用ndk编译,那么需要将变量转为long long型,否则会导致传递数据错误。
c. 返回ByteBuffer对象。先获取到ByteBuffer对象,再逐个获取ByteBuffer的关键属性,如array(),position(),remaining()。Java层方法:
签名:"()Ljava/nio/ByteBuffer;"。jni层调用代码:
d. 传入字符串
Java层方法:
签名:"([BI)V"。jni层调用代码:
Attach/Detach JVM线程可能存在风险,比如在notify_event函数中,在AttachCurrentThread后,如果因为疏忽,代码出现异常没有捕获导致没有运行到最后执行DetachCurrentThread,会造成程序直接崩溃,而且这样的写法造成代码冗余,不优雅。针对这个问题,我利用RAII技术使用一个类来封装Attach/Detach JVM线程的操作,即分别在构造和析构函数中Attach和Detach JVM线程:
这样只要函数内定义一个jvm_thread就安全的实现了Attach/Detach JVM线程的操作。由于jvm_thread构造函数内可能会抛出异常,因此需要加个捕获:
这样的思路在std::unique_lock中已经出现,std::unique_lock也利用RAII封装了线程锁上锁和解锁,保证上锁后一定会解锁。
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封装了线程锁上锁和解锁,保证上锁后一定会解锁。
相关文章推荐
- Jni 参数传递与操作——(C/C++ 代码与 java 代码的互相调用)
- 使用JNI进行混合编程:在C/C++中调用Java代码--------------------cocos2d-x 3.0正式版本(7.5)
- JNI 调用C++代码 并在C++代码中调用Java传递进来的接口
- 使用JNI进行混合编程:在C/C++中调用Java代码
- 用JNI进行Java编程---从C/C++程序调用Java代码
- 用JNI进行Java编程---从Java程序调用C/C++代码
- 使用JNI进行混合编程:在C/C++中调用Java代码
- 【转】使用JNI进行混合编程:在C/C++中调用Java代码
- 【转】使用JNI进行混合编程:在C/C++中调用Java代码
- windows下java JNI编程技巧——JAVA调用c/c++(4)
- JNI编程 —— 让C++和Java相互调用
- java使用JNI调用C++代码(vs2010生成dll文件)
- 使用JNI进行混合编程:在Java中调用C/C++本地库
- JNI编程 —— 让C++和Java相互调用
- JNI技术实践(1) Java调用C/C++代码
- java调用C++代码-JNI的使用
- JNI编程 —— 让C++和Java相互调用
- JNI编程 —— 让C++和Java相互调用
- Android-NDK开发之基础--Android JNI实例代码(一)-- 在JNI中执行Java方法--C/C++调用Java
- JNI、C/C++、java调用dll、java与c数据类型对应关系