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 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层来做:
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层调⽤代码:
}
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层调⽤代码:
}
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层调⽤代码:
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线程:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论