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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/cj5785/article/details/89048717

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 Language Type Native Type Description
boolean jboolean unsigned 8 bits
byte jbyte signed 8 bits
char jchar unsigned 16 bits
short jshort signed 16 bits
int jint signed 32 bits
long jlong signed 64 bits
float jfloat 32 bits
double jdouble 64 bits
void void N/A

引用数据类型

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

Java引用类型 JNI类型
String jstring
Object jobject
byte[] jByteArray
int[] jIntArray
String[] jobjectArray
Object[] jobjectArray

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

签名

Java Type Type Signature
boolean Z
byte B
char C
short S
int I
long J
float F
double D
void V
fully-qualified-class L 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));
	}
}

猜你喜欢

转载自blog.csdn.net/cj5785/article/details/89048717
今日推荐