zoukankan      html  css  js  c++  java
  • 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
    ", 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
    ", 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
    ", 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) + "
    ");
    	t.sortArray(arr);
    	System.out.print("排序后:" + Arrays.toString(arr) + "
    ");
    }
    

    }

    
    #### 返回数组
    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));
    	}
    }
    
  • 相关阅读:
    [npm]npm audit fix
    [javascript]中央定时器控制
    [javascript]并发模型与事件循环(Concurrency model and Event loop)
    [翻译][JavaScript] JavaScript 是不是一定是单线程的?
    [DOM][穿梭框][js]运用document.adoptNode方法,写出基础的穿梭框效果
    [document][DOM]document.importNode 与 document.adoptNode
    [DOM][document][进阶]DocumentFragment, document.createDocumentFragment()
    [Object][进阶]Object.defineProperty(),Object.defineProperties(),Object.getOwnPropertyDescriptor()
    [js][字符串]给字符串去空格(全角和半角)
    [vue]mixins在项目中的应用
  • 原文地址:https://www.cnblogs.com/cj5785/p/10664680.html
Copyright © 2011-2022 走看看