JNI的引入使java有了调用C/C++端代码的能力,然而在JNI中还有 一个非常重要的内容,那就是在C/C++本地
代码中访问Java端的代码,一个常见的应用就是获取类的属性和调用类的方法,为了在C/C++中表示属性和方法,JNI
在jni.h头文件中定义了jfieldId,jmethodID类型来分别代表Java端的属性和方法。
我们在访问,或者设置Java属性的时候,首先就要先在本地代码取得代表该Java属性的jfieldID,然后才能在本地代码
中进行Java属性操作,同样的,我们需要呼叫Java端的方法时,也是需要取得代表该方法的jmethodID才能进行Java
方法调用。
使用JNIEnv的:
GetFieldID/GetMethodID
GetStaticFieldID/GetStaticMethodID
来取得相应的jfieldID和jmethodID。
下面来具体看一下这几个方法:
GetFieldID(jclass clazz,const char* name,const char* sign)
方法的参数说明:
clazz:这个简单就是这个方法依赖的类对象的class对象
name:这个是这个字段的名称
sign:这个是这个字段的签名(我们知道每个变量,每个方法都是有签名的)
签名的规则如下:
数据类型 | 签名 |
---|---|
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | J |
float | F |
double | D |
void | V |
object | L开头,然后以/分割包的完整类型,再加;号,如Ljava/lang/String; |
Array | 以[开头,再加上数组元素的签名,比如int[],签名就是[I,Object数组签名就是[Ljava/lang/Object; |
来看一个使用签名获得属性和方法的例子:
Java代码:
public class Student {
public int age;
public void setBirthday(int age, Date date, int[] arr){
System.out.println("print setBirthday function");
}
public native void test();
}
Jni代码:
extern "C" JNIEXPORT jstring JNICALL
Java_com_demo_Student_test(
JNIEnv *env,
jobject thiz
) {
jclass student_clz = env->GetObjectClass(thiz);
jfieldID age_field = env->GetFieldID(student_clz, "age", "I");
jmethodID set_method = env->GetMethodID(student_clz, "setBirthday", "(ILjava/util/Date;[I)V");
env->CallVoidMethod(thiz, set_method, 0, NULL, NULL);
}
其中的 "I" 和 "(ILjava/util/Date;[I)V" 就是属性和方法的签名
Jni调用java的方法跟反射有点类似,我们对比一下反射的调用方式:
public static void main(String[] args) throws Exception {
ClassField obj = new ClassField();
obj.setStr("Test");
// 获取ClassField字节码对象的Class引用
Class<?> clazz = obj.getClass();
// 获取str属性
Field field = clazz.getDeclaredField("str");
// 取消权限检查,因为Java语法规定,非public属性是无法在外部访问的
field.setAccessible(true);
// 获取obj对象中的str属性的值
String str = (String)field.get(obj);
System.out.println("str = " + str);
}
所以我们在本地代码中调用JNI函数访问Java对象中某一个
属性的时候,首先第一步就是要获取该对象的Class引用,然后在Class中查找需要访问的字段ID,最后调用JNI函数的
GetXXXField系列函数,获取字段(属性)的值。
JNI访问字符串
java内部使用的是utf-16 16bit 的编码方式
jni 里面使用的utf-8 unicode编码方式 英文是1个字节,中文 3个字节
C/C++ 使用 ascii编码 ,中文的编码方式 GB2312编码 中文 2个字节
Java代码:
public native static String sayHello(String text);
C/C++代码:
JNIEXPORT jstring JNICALL Java_JString_sayHello
(JNIEnv * env, jclass jclaz, jstring jstr) {
const char * c_str = NULL;
char buf[128] = {0};
jboolean iscopy;
c_str = (*env)->GetStringUTFChars(env, jstr, &iscopy);
printf("isCopy:%d\n", iscopy);
if(c_str == NULL) {
return NULL;
}
printf("C_str: %s \n", c_str);
sprintf(buf,"Hello, 你好 %s", c_str);
printf("C_str: %s \n", buf);
(*env)->ReleaseStringUTFChars(env, jstr, c_str);
return (*env)->NewStringUTF(env,buf);
}
一 、访问Java成员变量
Java成员变量一般有两类:静态和非静态。所以在JNI中对这两种不同的类型就有了两种不太相同的调用方法。
1.访问非静态成员
java代码
public int property;
Jni代码:
JNIEXPORT void JNICALL Java_Hello_testField(JNIEnv *env, jobject jobj) {
jclass claz = (*env)->GetObjectClass(env,jobj);
jfieldID jfid = (*env)->GetFieldID(env, claz, "property","I");
jint va = (*env)->GetIntField(env,jobj, jfid);
printf("va: %d", va);
(*env)->SetIntField(env, jobj, jfid, va + 100);
}
上例中,首先调用GetObjectClass函数获取ClassField的Class引用:
clazz = (*env)->GetObjectClass(env,obj);
然后调用GetFieldID函数从Class引用中获取字段的ID(property是字段名,I是字段的签名)
jfieldID jfid = (*env)->GetFieldID(env, claz, "property","I");
最后调用GetIntField函数,传入实例对象和字段ID,获取属性的值
jint va = (*env)->GetIntField(env,jobj, jfid);
如果要修改这个值就可以使用SetIntField函数:
(*env)->SetIntField(env, jobj, jfid, va + 100);
2.访问静态成员
访问静态变量和实例变量不同的是,获取字段ID使用GetStaticFieldID,获取和修改字段的值使用
Get/SetStaticXXXField系列函数,比如上例中获取和修改静态变量num:
num = (*env)->GetStaticIntField(env,clazz,fid);
// 修改静态变量num的值
(*env)->SetStaticIntField(env, clazz, fid, 80);
二、访问Java中的函数
Java成员函数一般有两类:静态和非静态。所以在JNI中对这两种不同的类型就有了两种不太相同的调用方法,这两
种不同类型虽然他们的调用方式有些许不同,但是,他们的实质上是一样的。只是调用的接口的名字有区别,而对于
流程是没有区别的。
Java代码如下:
private static void callStaticMethod(String str, int i) {
System.out.format("ClassMethod::callStaticMethod called!-->str=%s," +
" i=%d\n", str, i);
}
JNI代码如下:
JNIEXPORT void JNICALL Java_JniTest_callJavaStaticMethod(JNIEnv *env, jobject jobje){
jclass clz = (*env)->FindClass(env,"ClassMethod");
if(clz == NULL) {
printf("clz is null");
return;
}
jmethodID jmeid = (*env)->GetStaticMethodID(env, clz, "callStaticMethod", "
(Ljava/lang/String;I)V");
if(jmeid == NULL) {
printf("jmeid is null");
return;
}
jstring arg = (*env)->NewStringUTF(env, "我是静态类");
(*env)->CallStaticVoidMethod(env,clz,jmeid,arg,100);
(*env)->DeleteLocalRef(env,clz);
(*env)->DeleteLocalRef(env,arg);
}
总结
- 调用GetObjectClass函数获取实例对象的Class引用
- 调用GetFieldID函数获取Class引用中某个实例变量的ID
- 调用GetXXXField函数获取变量的值,需要传入实例变量所属对象和变量ID
- 调用SetXXXField函数修改变量的值,需要传入实例变量所属对象、变量ID和变量的值
- 调用FindClass函数获取类的Class引用
- 调用GetStaticFieldID函数获取Class引用中某个静态变量ID
- 调用GetStaticXXXField函数获取静态变量的值,需要传入变量所属Class的引用和变量ID
- 调用SetStaticXXXField函数设置静态变量的值,需要传入变量所属Class的引用、变量ID和变量的值