Android NDK开发笔记四:Java和c/c++的相互调用

       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);
}

总结

1 、由于 JNI 函数是直接操作 JVM 中的数据结构,不受 Java 访问修饰符的限制。即,在本地代码中可以调用 JNI 函数可以
访问 Java 对象中的非 public 属性和方法
 
2 、访问和修改实例变量操作步聚:
  • 调用GetObjectClass函数获取实例对象的Class引用
  • 调用GetFieldID函数获取Class引用中某个实例变量的ID
  • 调用GetXXXField函数获取变量的值,需要传入实例变量所属对象和变量ID
  • 调用SetXXXField函数修改变量的值,需要传入实例变量所属对象、变量ID和变量的值
  •  
3 、访问和修改静态变量操作步聚:
  • 调用FindClass函数获取类的Class引用
  • 调用GetStaticFieldID函数获取Class引用中某个静态变量ID
  • 调用GetStaticXXXField函数获取静态变量的值,需要传入变量所属Class的引用和变量ID
  • 调用SetStaticXXXField函数设置静态变量的值,需要传入变量所属Class的引用、变量ID和变量的值
发布了27 篇原创文章 · 获赞 24 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/shving/article/details/103107203