NDK学习笔记-JNI数据类型和属性方法的访问

JNI实现了C/C++与Java的相互访问,那么这篇文章就从C/C++访问Java开始说起

native函数说明

每个native函数,都至少有两个参数(JNIEnv *jclassjobject)

  • 当native方法为静态方法时,采用jclass,此时jclass代表native方法所属类的class对象
  • 当native方法为非静态时,使用jobject,此时jobject代表native方法所属对象

JNI数据类型

基本数据类型

Java的基本数据类型与JNI数据类型成映射关系
Java类型 <=> JNI类型 <=> C类型

Java LanguageType NativeType Description
booleanjbooleanunsigned 8 bits
bytejbytesigned 8 bits
charjcharunsigned 16 bits
shortjshortsigned 16 bits
intjintsigned 32 bits
longjlongsigned 64 bits
floatjfloat32 bits
doublejdouble64 bits
voidvoidN/A

引用数据类型

Java的引用类型与JNI的对应关系

Java引用类型JNI类型
Stringjstring
Objectjobject
byte[]jByteArray
int[]jIntArray
String[]jobjectArray
Object[]jobjectArray

值得注意的是:普通数据类型的数组,其在JNI中的表现类似,表格中列举出两个,字符串数组属于Object数组,其表现形式一样
JNI数据类型和属性方法的访问-引用类型

签名

Java TypeType Signature
booleanZ
byteB
charC
shortS
intI
longJ
floatF
doubleD
voidV
fully-qualified-classL fully-qualified-class;
type[][ type
method type(arg-types) ret-type

说明:
Object:L开头,然后以/分隔包的完整类型,后面再加;,比如String签名就是Ljava/lang/String;
Array:以[开头,再加上数组元素类型的签名,比如int[]签名就是[I,再比如int[][]的签名就是[[I,Object数组的签名就是[Ljava/lang/Object;
使用javap -s -p 完整类名可得到所有签名,需要在bin目录下

调用Java属性

访问非静态属性

在Java中存在

private String key = "jack";
public native String accessFiled(); //触发Java访问C/C++使其在底层修改并返回

非静态属性先得到class,再对其进行操作
Get和Set都有规律可循,GetField和SetField

JNIEXPORT jstring JNICALL Java_com_cj5785_jni_JniTest_accessField
(JNIEnv *env, jobject jobj)
{
	//获取到JniTest.class
	jclass cls = (*env)->GetObjectClass(env, jobj);
	//属性名称,属性签名
	jfieldID fid = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String;");
	//获取key属性的值
	jstring jstr = (*env)->GetObjectField(env, jobj, fid);
	//jstring转化为C的字符串
	char *c_str = (char *)(*env)->GetStringUTFChars(env, jstr, NULL);
	//C语言处理:字符串拼接
	char text[20] = "super ";
	strcat(text, c_str);
	//将C的字符串转化为jstring
	jstring new_string = (*env)->NewStringUTF(env, text);
	//修改key
	(*env)->SetObjectField(env, jobj, fid, new_string);
    //释放资源
    (*env)->ReleaseStringUTFChars(env, jstr, c_str);
	return new_string;
}

在Java中调用

package com.cj5785.jni;

public class JniTest {
	
	static {
		System.loadLibrary("JNITest");
	}

	private String key = "test";
	
	public native String accessField();

	public static void main(String[] args) {
		JniTest t = new JniTest();
		System.out.println("修改前:" + t.key);
		t.accessField();
		System.out.println("修改后:" + t.key);
	}
}

访问静态属性

在Java中存在

public static int count = 1;
public native void accessStaticField();

在native函数中修改

JNIEXPORT void JNICALL Java_com_cj5785_jni_JniTest_accessStaticField
(JNIEnv *env, jobject jobj)
{
	jclass cls = (*env)->GetObjectClass(env, jobj);
	jfieldID fid = (*env)->GetStaticFieldID(env, cls, "count", "I");
	jint count = (*env)->GetStaticIntField(env, cls, fid);
	count++;
	(*env)->SetStaticIntField(env, cls, fid, count);
}

在Java中访问

package com.cj5785.jni;

public class JniTest {
	
	static {
		System.loadLibrary("JNITest");
	}
	
	public static int count = 1; 
	
	public native void accessStaticField();

	public static void main(String[] args) {
		JniTest t = new JniTest();
		System.out.println("修改前:" + JniTest.count);
		t.accessStaticField();
		System.out.println("修改后:" + JniTest.count);
	}
}

访问属性总结

  • 如果为非静态属性,经历以下步骤

    • GetObjectClass
    • GetFieldID
    • Get<Type>Field
    • 中间处理过程
    • Set<Type>Field
  • 如果为静态属性,经历以下步骤

    • GetObjectClass
    • GetStaticFieldID
    • GetStatic<Type>Field
    • 中间处理过程
    • SetStatic<Type>Field

调用Java方法

访问非静态方法

Java中存在

public native void accessMethod();
public int getRandomInt(int max) {
    System.out.println("···getRandomInt run···");
    return new Random().nextInt(max);
}

在native中调用

JNIEXPORT void JNICALL Java_com_cj5785_jni_JniTest_accessMethod
(JNIEnv *env, jobject jobj)
{
    //jclass
	jclass cls = (*env)->GetObjectClass(env, jobj);
    //jmethodID
	jmethodID mid = (*env)->GetMethodID(env, cls, "getRandomInt", "(I)I");
    //Call<Type>Method
	jint random = (*env)->CallIntMethod(env, jobj, mid, 100);
	printf("%ld\n", random);
}

在Java中触发

package com.cj5785.jni;

import java.util.Random;

public class JniTest {
	
	static {
		System.loadLibrary("JNITest");
	}
	
	public native void accessMethod();
	
	public int getRandomInt(int max) {
		System.out.println("···getRandomInt run···");
		return new Random().nextInt(max);
	}

	public static void main(String[] args) {
		JniTest t = new JniTest();
		t.accessMethod();
	}
}

访问静态方法

Java中存在

public native void accessStaticMethod();
public static String getUUID() {
    System.out.println("···getUUID run···");
    return UUID.randomUUID().toString();
}

在native中调用

JNIEXPORT void JNICALL Java_com_cj5785_jni_JniTest_accessStaticMethod
(JNIEnv *env, jobject jobj)
{
	jclass cls = (*env)->GetObjectClass(env, jobj);
	jmethodID mid = (*env)->GetStaticMethodID(env, cls, "getUUID", "()Ljava/lang/String;");
	jstring jstr = (*env)->CallStaticObjectMethod(env, cls, mid);
	char *uuid_str = (*env)->GetStringUTFChars(env, jstr, NULL);
	printf("%s\n", uuid_str);
    (*env)->ReleaseStringUTFChars(env, jstr, uuid_str);
}

在Java中触发

package com.cj5785.jni;

import java.util.UUID;

public class JniTest {
	
	static {
		System.loadLibrary("JNITest");
	}
	
	public native void accessStaticMethod();
	
	public static String getUUID() {
		System.out.println("···getUUID run···");
		return UUID.randomUUID().toString();
	}

	public static void main(String[] args) {
		JniTest t = new JniTest();
		t.accessStaticMethod();
	}
}

访问方法总结

  • 如果为非静态方法,经历以下步骤

    • GetObjectClass
    • GetMethodID
    • Call<Type>Method
    • 处理过程
  • 如果为静态属性,经历以下步骤

    • GetObjectClass
    • GetStaticMethodID
    • GetStatic<Type>Method
    • 处理过程

访问构造方法

使用Date类的getTime()方法,产生当前时间戳
在native中调用Date的getTime方法

JNIEXPORT jobject JNICALL Java_com_cj5785_jni_JniTest_accessConstructor
(JNIEnv *env, jobject jobj)
{
	jclass cls = (*env)->FindClass(env, "java/util/Date");
	jmethodID construcyor_mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
	jobject date_obj = (*env)->NewObject(env, cls, construcyor_mid);
	jmethodID mid = (*env)->GetMethodID(env, cls, "getTime", "()J");
	jlong time = (*env)->CallLongMethod(env, date_obj, mid);
	printf("%lld\n", time);
	return date_obj;
}

在Java中触发

package com.cj5785.jni;

import java.util.Date;

public class JniTest {
	
	static {
		System.loadLibrary("JNITest");
	}

	public native Date accessConstructor();

	public static void main(String[] args) {
		JniTest t = new JniTest();
		t.accessConstructor();
	}
}

访问构造方法,分成以下几个步骤

  • FindClass
  • GetMethodID:初始化
  • NewObject
  • GetMethodID
  • CallMethod

调用父类方法

在Java中存在Person和Student两个类

public class Person {
	public void say() {
		System.out.println("Person Class");
	}
}
public class Student extends Person {
	@Override
	public void say() {
		System.out.println("Student Class");
	}
}

在native中调用子类方法,获取父类方法

JNIEXPORT void JNICALL Java_com_cj5785_jni_JniTest_accessNonvirtualMethod
(JNIEnv *env, jobject jobj)
{
	jclass cls = (*env)->GetObjectClass(env, jobj);
	jfieldID fid = (*env)->GetFieldID(env, cls, "person", "Lcom/cj5785/jni/Person;");
	jobject person_obj = (*env)->GetObjectField(env, jobj, fid);
	jclass person_cls = (*env)->FindClass(env, "com/cj5785/jni/Person");
	jmethodID mid = (*env)->GetMethodID(env, person_cls, "say", "()V");
	//执行子类方法
	(*env)->CallObjectMethod(env, person_obj, mid);
	//执行父类方法
	(*env)->CallNonvirtualObjectMethod(env, person_obj, person_cls, mid);
}

在Java中触发

package com.cj5785.jni;

import java.util.Date;
import java.util.Random;
import java.util.UUID;

public class JniTest {
	
	static {
		System.loadLibrary("JNITest");
	}
	
	public Person person = new Student();
	
	public native void accessNonvirtualMethod();
	
	public static void main(String[] args) {
		JniTest t = new JniTest();
		t.accessNonvirtualMethod();
	}
}

调用父类方法步骤

  • GetObjectClass:获取class对象
  • GetFieldID:获取属性(对象)
  • GetField:获取
  • FindClass:查找父类
  • GetMethodID:获取方法
  • CallMethod(子类方法)或CallNonvirtualMethod(父类方法)

字符串乱码问题

在Java存在

public native String chineseChar(String str);

在native中产生的字符串,当返回时可能会产生乱码问题,这是由于编码格式不同造成的
在Java中传入字符串,那么在native如果不处理,直接返回,那么不会出现乱码

JNIEXPORT jstring JNICALL Java_com_cj5785_jni_JniTest_chineseChar
(JNIEnv *env, jobject jobj, jstring jstr)
{
	//使用GetStringUTFChars返回
	char *c_str = (*env)->GetStringUTFChars(env, jstr, NULL);
	return (*env)->NewStringUTF(env, c_str);
}

但如果对其进行过处理,那么返回的中文字符则会出现乱码问题
以下两例,一个是在输入的字符串做了追加字符,一个是做了新字符串返回,都存在中文,返回的结果都出现了乱码

JNIEXPORT jstring JNICALL Java_com_cj5785_jni_JniTest_chineseChar
(JNIEnv *env, jobject jobj, jstring jstr)
{
	//使用GetStringUTFChars返回
	//char *c_str = (*env)->GetStringUTFChars(env, jstr, NULL);
	//strcat(c_str, "追加");
	//return (*env)->NewStringUTF(env, c_str);

	//使用C转为jstring,然后返回
	char *c_str = "native:中文测试";
	return (*env)->NewStringUTF(env, c_str);
}

这种情况,有两种解决办法,一种是在C中寻找字符串转码的工具,另一种是直接调用Java的转码工具,前者难度较大,在这里采用后者

JNIEXPORT jstring JNICALL Java_com_cj5785_jni_JniTest_chineseChar
(JNIEnv *env, jobject jobj, jstring jstr)
{
	//使用Java的字符串转码工具
	char *c_str = "native:中文测试";
	//获取jmethod
	jclass strcls = (*env)->FindClass(env, "java/lang/String");
	jmethodID constructor_mid = (*env)->GetMethodID(env, strcls, "<init>", "([BLjava/lang/String;)V");
	//C数组转JNI数组
	jbyteArray bytes = (*env)->NewByteArray(env, strlen(c_str));
	//数组赋值
	(*env)->SetByteArrayRegion(env, bytes, 0, strlen(c_str), c_str);
	//设置字符编码
	jstring charsetName = (*env)->NewStringUTF(env, "GB2312");
	//调用构造方法,返回编码后的jstring
	return (*env)->NewObject(env, strcls, constructor_mid, bytes, charsetName);
}

在Java中触发

package com.cj5785.jni;

public class JniTest {
	
	static {
		System.loadLibrary("JNITest");
	}

	public native String chineseChar(String str);

	public static void main(String[] args) {
		JniTest t = new JniTest();
		System.out.println(t.chineseChar("中文测试"));
	}
}

传入数组的处理

传入int数组,并对其排序

Java中存在native方法

public native void sortArray(int[] array);

在native函数中处理

int compare(int *a, int *b)
{
	return (*a - *b);
}

JNIEXPORT void JNICALL Java_com_cj5785_jni_JniTest_sortArray
(JNIEnv *env, jobject jobj, jintArray array)
{
	//jintArray转化为C int数组
	jint *elems = (*env)->GetIntArrayElements(env, array, NULL);
	//获取数组长度
	int len = (*env)->GetArrayLength(env, array);
	//排序
	qsort(elems, len, sizeof(jint), compare);
	//刷新数组
	(*env)->ReleaseIntArrayElements(env, array, elems, JNI_COMMIT);
}

关于数组刷新的同步问题

mode更新Java数组释放C/C++数组
`0``√``√`
`JNI_ABORT``×``√`
`JNI_COMMIT``√``×`
在Java中调用 ```java package com.cj5785.jni;

import java.util.Arrays;

public class JniTest {

static {
	System.loadLibrary("JNITest");
}

public native void sortArray(int[] array);

public static void main(String[] args) {
	JniTest t = new JniTest();
	int[] arr = {5,12,3,6,9,25,1};
	System.out.print("排序前:" + Arrays.toString(arr) + "\n");
	t.sortArray(arr);
	System.out.print("排序后:" + Arrays.toString(arr) + "\n");
}

}


#### 返回数组
Java中存在native方法
```java
public native int[] getArray(int len);

在native中生成数组

JNIEXPORT jintArray JNICALL Java_com_cj5785_jni_JniTest_getArray
(JNIEnv *env, jobject jobj, jint len)
{
	//生成jint数组
	jintArray jint_arr = (*env)->NewIntArray(env, len);
	//获取数组元素
	jint *elems = (*env)->GetIntArrayElements(env, jint_arr, NULL);
	//为数组赋值
	int i = 0;
	for (; i < len; i++)
	{
		elems[i] = i;
	}
	//同步数组
	(*env)->ReleaseIntArrayElements(env, jint_arr, elems, 0);
	//返回生成的数组
	return jint_arr;
}

在Java中调用生成数组的方法

package com.cj5785.jni;

import java.util.Arrays;

public class JniTest {
	
	static {
		System.loadLibrary("JNITest");
	}
	
	public native int[] getArray(int len);

	public static void main(String[] args) {
		JniTest t = new JniTest();
		int newArr[] = t.getArray(10);
		System.out.println(Arrays.toString(newArr));
	}
}
posted @ 2019-04-05 22:49  cj5785  阅读(242)  评论(0编辑  收藏  举报