你应该了解的JNI知识——Java与JNI互相调用
Posted xingfeng_coder
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了你应该了解的JNI知识——Java与JNI互相调用相关的知识,希望对你有一定的参考价值。
在你应该了解的JNI知识(一)——静态注册与动态注册中,了解了JNI是如何使用的,以及两种注册方式的使用以及区别。本篇博客将介绍Java和JNI的互相调用,因此主要包括两部分:
- JNI层调用Java层
- Java层调用JNI、Native层
JNI层调用Java层
JNI层调用Java层有点类似Java的反射机制,需要首先找到类、再找到某个方法或字段,再进行调用。
这里涉及JNIEnv的几个方法:
//根据全限定名找到类
jclass FindClass(const char* name)
//根据方法名和方法参数的签名得到方法id
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
//从方法名可以看到,该方法是获取静态方法的
jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig)
//Java层的方法返回不同类型,需要调用不同的方法
_jtype Call##_jname##MethodA(jobject obj, jmethodID methodID, \\
jvalue* args)
//根据字段名得到字段id
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
...
上面的方法主要包括得到jclass类、jmethodId、jfieldId,是不是和Java的反射好像?
有点需要注意的是,JNI层的方法严格区分了返回类型,返回类型是boolean的,会有CallBoolenMethodId,返回类型是Int的,会有CallIntMethodId;同理关于FieldId和数组的方法都是这样的,不能调用错。
这边以一个demo为例:Java层提供了三个方法:JNI层首先调用两个方法得到两个数,然后相加,再调用Java层更新界面。
MainActivity的代码
class MainActivity : AppCompatActivity()
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btnJniInvokeJava.setOnClickListener
jniInvokeJava()
external fun jniInvokeJava()
fun getNum1(): Int
return 10
fun getNum2(): Double
return 20.0
fun showResult(value: Double)
tvResultShow.text = value.toString()
companion object
init
System.loadLibrary("jniAndJava")
JNI层的代码
这里采用静态注册的方式,方法的的实现如下:
//调用MainActivity中的两个方法,得到两个数,相加,再显示到TextView上
JNIEXPORT void JNICALL
Java_com_enniu_jnidemo_MainActivity_jniInvokeJava(JNIEnv *env, jobject thiz)
//找到MainActivity类
jclass mainActivityClazz = env->FindClass("com/enniu/jnidemo/MainActivity");
//找到getNum1()方法
jmethodID getNum1MethodId = env->GetMethodID(mainActivityClazz, "getNum1", "()I");
//找到getNum2()方法
jmethodID getNum2MethodId = env->GetMethodID(mainActivityClazz, "getNum2", "()D");
//因为getNum1()返回值是Int,所以调用CallIntMethod
jint num1=env->CallIntMethod(thiz,getNum1MethodId);
//因为getNum2()返回值是Double,所以调用CallDoubleMethod
jdouble num2=env->CallDoubleMethod(thiz,getNum2MethodId);
//两数相加
jdouble result=num1+num2;
//设置给TextView
jmethodID showResultMethodId=env->GetMethodID(mainActivityClazz,"showResult","(D)V");
//showResult()方法的返回值是void,所以调用CallVoidMethod()方法
env->CallVoidMethod(thiz,showResultMethodId,result);
从上面可以看到,整体调用流程和反射是很像的。Call***Method()的第一个参数是jobject,表示在某个对象上调用该方法,因此如果需要调用对象的方法,JNI又无法获取的话,需要从Java层传入。
jclass GetObjectClass(jobject obj)
上面这个方法提供了从jobject–>jclass的快捷方式,就不需要走FindClass()的步骤了,这里是不是发现
getObjectClass====Object.getClass()
FindClass()====Class.forName()
是不是很相似?因此很多时候我们要学会类比,这样记忆和理解起来会比较快。
Java层调用C/C++代码
这里可以标题取得有所歧义,因为JNI不就是Java调用C/C++吗?这里的情形可以举个例子:比如说需要在C++层创建多份同一个对象,Java层会根据不同情况调用不同对象,那么该怎么做呢?
Java层要能调用不同对象,得保存各个对象的信息,但那是C层的对象,怎么保存了?答案是指针(对象地址),然后根据不同对象传入不同指针即可。如果C++层需要保存对象,可以使用vector或map来进行保存。
举个例子:C++层有Person类,Java层去创建Person类、设置和获取name字段。
MainActivity代码
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btnJavaInvokeCpp.setOnClickListener
//创建一个Person类并设置name
val person1 = createPerson()
setPersonName(person1, "Hello")
//再创建一个Person类并设置name
val person2 = createPerson()
setPersonName(person2, "World")
//获取Person类的name字段
tvResultShow.text = "$getPersonName(person1),$getPersonName(person2)"
//三个native方法声明
external fun createPerson(): Long
external fun setPersonName(ptr: Long, name: String)
external fun getPersonName(ptr: Long): String
JNI层的代码
创建了一个Person类,有一个字段name。jni中对应的三个代码如下:
JNIEXPORT jlong JNICALL
Java_com_enniu_jnidemo_MainActivity_createPerson(JNIEnv *env, jobject thiz, jstring name)
Person *p = new Person();
//返回指针,供Java层保存
return reinterpret_cast<uintptr_t>(p);
JNIEXPORT void JNICALL
Java_com_enniu_jnidemo_MainActivity_setPersonName(JNIEnv *env, jobject thiz, jlong ptr,
jstring name)
//强转到Person指针
Person *person = reinterpret_cast<Person *>(ptr);
person->setName(env->GetStringUTFChars(name, NULL));
JNIEXPORT jstring JNICALL
Java_com_enniu_jnidemo_MainActivity_getPersonName(JNIEnv *env, jobject thiz, jlong ptr)
//强转到Person指针
Person *person = reinterpret_cast<Person *>(ptr);
return env->NewStringUTF(person->getName());
可以看到在createPerson()方法中创建一个Person类,然后返回其指针,set和get方法首先都是通过Java层传入的指针强转到Person对象,再进行操作的。
总结
至此,介绍完了Java与JNI代码的互相调用。
JNI调用Java代码是一种类似反射的原理,先找到jclass、再找到jmethodId,然后调用,这样一步步地来;Java调用C/C++代码创建对象是需要保存对象指针,然后各种操作是要将指针传入到jni层,然后强转到具体对象再进行操作的。
关于代码,可以移步Github
关注我的技术公众号,不定期会有技术文章推送,不敢说优质,但至少是我自己的学习心得。微信扫一扫下方二维码即可关注:
以上是关于你应该了解的JNI知识——Java与JNI互相调用的主要内容,如果未能解决你的问题,请参考以下文章