JNI 的数据类型以及和Java层之间的数据转换

JNI的数据类型和类型签名

数据类型

JNI的数据类型包含两种:基本类型引用类型

基本类型主要有jbooleanjcharjint等,它们和Java中的数据类型的对应关系如下表所示。

JNI 的数据类型以及和Java层之间的数据转换_第1张图片

JNI中的引用类型主要有类、对象和数组,它们和Java中的引用类型的对应关系如下表所示。

JNI 的数据类型以及和Java层之间的数据转换_第2张图片

当然,JNI 中还有个 Java 中没有的 jsize,定义如下:

typedef jint jsize;

其实jsize整型是用来描述基本指标和大小,没有什么神秘的。

类型签名

JNI的类型签名标识了一个特定的Java类型,这个类型既可以是类和方法,也可以是数据类型。

类的签名比较简单,它采用 L+包名+类名+; 的形式,只需要将其中的替换为/即可。比如java.lang.String,它的签名为Ljava/lang/String;,注意末尾的也是签名的一部分。

基本数据类型的签名采用一系列大写字母来表示,如下表所示。

JNI 的数据类型以及和Java层之间的数据转换_第3张图片

从上表可以看出,基本数据类型的签名是有规律的,一般为首字母的大写,但是boolean除外,因为B已经被byte占用了,而long的签名之所以不是L,那是因为L表示的是类的签名。

对象和数组的签名稍微复杂一些。对于对象来说,它的签名就是对象所属的类的签名,比如String对象,它的签名为Ljava/lang/String;。对于数组来说,它的签名为[+类型签名,比如int数组,其类型为int,而int的签名为I,所以int数组的签名就是[I,同理就可以得出如下的签名对应关系:

char[]       [C
float[]      [F
double[]     [D
long[]       [J
String[]     [Ljava/lang/String;
Object[]     [Ljava/lang/Object;

对于多维数组来说,它的签名为n个[+类型签名,其中n表示数组的维度,比如,int[][]的签名为[[I,其他情况可以依此类推。

方法的签名为(参数类型签名)+返回值类型签名,这有点不好理解。举个例子,如下方法:boolean fun1(int a, double b, int[] c),根据签名的规则可以知道,它的参数类型的签名连在一起是ID[I,返回值类型的签名为Z,所以整个方法的签名就是(ID[I)Z。再举个例子,下面的方法:boolean fun1(int a, String b, int[] c),它的签名是(ILjava/lang/String; [I)Z。为了能够更好地理解方法的签名格式,下面再给出两个示例:

int fun1()        签名为 ()I
void fun1(int i)  签名为 (I)V

一个Java类的方法的Signature可以通过javap命令获取:javap -s -p Java类名

本地方法中访问java程序中的内容

1. 访问 String 对象

从java程序中传过去的String对象在本地方法中对应的是jstring类型,jstring类型和c中的char*不同,所以如果你直接当做 char*使用的话,就会出错。因此在使用之前需要将jstring转换成为c/c++中的char*,这里使用JNIEnv的方法转换。下面是一个例子:

JNIEXPORT jstring JNICALL Java_Prompt_getLine(JNIEnv *env, jobject obj, jstring prompt)
{
  char buf[128];
  const char *str = (*env)->GetStringUTFChars(env, prompt, 0);
  printf("%s", str);
  (*env)->ReleaseStringUTFChars(env, prompt, str);
}

这里使用GetStringUTFChars方法将传进来的promptjstring类型)转换成为UTF-8的格式,就能够在本地方法中使用了。

注意:在使用完你所转换之后的对象之后,需要显示调用ReleaseStringUTFChars方法,让JVM释放转换成UTF-8的string的对象的空间,如果不显示的调用的话,JVM中会一直保存该对象,不会被垃圾回收器回收,因此就会导致内存溢出。

下面是访问String的一些方法:

  • GetStringUTFCharsjstring转换成为UTF-8格式的char*
  • GetStringCharsjstring转换成为Unicode格式的char*
  • ReleaseStringUTFChars释放指向UTF-8格式的char*的指针
  • ReleaseStringChars释放指向Unicode格式的char*的指针
  • NewStringUTF创建一个UTF-8格式的String对象
  • NewString创建一个Unicode格式的String对象
  • GetStringUTFLength获取UTF-8格式的char*的长度
  • GetStringLength获取Unicode格式的char*的长度

2. 访问 Array 对象

和String对象一样,在本地方法中不能直接访问jarray对象,而是使用JNIEnv指针指向的一些方法来使用。
  
访问Java原始类型数组:

  • 1)获取数组的长度:
JNIEXPORT jint JNICALL Java_IntArray_sumArray(JNIEnv *env, jobject obj, jintArray arr)
{
 	int i, sum = 0;
  jsize len = (*env)->GetArrayLength(env, arr);
}

这里获取数组的长度和普通的c语言中的获取数组长度不一样,这里使用JNIEnv的一个函数GetArrayLength

  • 2)获取一个指向数组元素的指针:
jint *body = (*env)->GetIntArrayElements(env, arr, 0);

使用GetIntArrayElements方法获取指向arr数组元素的指针,注意该函数的参数,第一个是JNIEnv,第二个是数组,第三个是数组里面开始的元素。

  • 3)使用指针取出 Array 中的元素
for (i=0; i<len; i++) {
 sum += body[i];
}

这里使用就和普通的c中的数组使用没有什么不同了

  • 4)释放数组元素的引用
(*env)->ReleaseIntArrayElements(env, arr, body, 0);

和操作String中的释放String的引用是一样的,提醒JVM回收arr数组元素的引用。

这里举的例子是使用int数组的,同样还有boolean、float等对应的数组。

获取数组和释放数组元素指针的对应关系:

数组类型 获取函数 释放函数
boolean GetBooleanArrayElements ReleaseBooleanArrayElements
byte GetByteArrayElements ReleaseByteArrayElements
char GetCharArrayElements ReleaseCharArrayElements
short GetShortArrayElements ReleaseShortArrayElements
int GetIntArrayElements ReleaseIntArrayElements
long GetLongArrayElements ReleaseLongArrayElements
float GetFloatArrayElements ReleaseFloatArrayElements
double GetDoubleArrayElements ReleaseDoubleArrayElements

  • GetObjectArrayElement returns the object element at a given index.
  • SetObjectArrayElement updates the object element at a given index.

3. 访问Java对象的方法

JNI调用Java方法的流程是先通过类名找到类,然后再根据方法名找到方法的id,最后就可以调用这个方法了。如果是调用Java中的非静态方法,那么需要构造出类的对象后才能调用它。
  
在本地方法中调用Java对象的方法的步骤:
  
① 获取你需要访问的Java对象的Class类:

jclass cls = (*env)->GetObjectClass(env, obj);

使用GetObjectClass方法获取obj对应的jclass
  
② 获取MethodID

jmethodID mid = (*env)->GetMethodID(env, cls, "callback", "(I)V");

使用GetMethdoID方法获取你要使用的方法的MethdoID。其参数的意义:

  • envJNIEnv
  • cls:第一步获取的jclass
  • "callback":要调用的方法名
  • "(I)V":方法的Signature

③ 调用方法:

(*env)->CallVoidMethod(env, obj, mid, depth);

使用CallVoidMethod方法调用方法。参数的意义:

  • envJNIEnv指针
  • obj:调用该native方法的jobject对象
  • mid:方法的methodID(即第二步获得的MethodID
  • depth:方法需要的参数(对应方法的需求,添加相应的参数),可以是可变参数

注:这里使用的是CallVoidMethod方法调用,因为没有返回值,如果有返回值的话要使用对应的方法。

除了CallVoidMethod外,针对每种基本类型的方法都有不同的重载,如下:

jobject     (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);  
jobject     (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
jobject     (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
jboolean    (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);  
jboolean    (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
jboolean    (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
jbyte       (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);  
jbyte       (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
jbyte       (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
jchar       (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);  
jchar       (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
jchar       (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
jshort      (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);  
jshort      (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
jshort      (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
jint        (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);  
jint        (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
jint        (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
jlong       (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);  
jlong       (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
jlong       (*CallLongMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
jfloat      (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...);  
jfloat      (*CallFloatMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
jfloat      (*CallFloatMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
jdouble     (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...);  
jdouble     (*CallDoubleMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
jdouble     (*CallDoubleMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  
void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);  
void        (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list);  
void        (*CallVoidMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);  

给调用的函数传参数:

通常我们直接在methodID后面将要传的参数添加在后面,但是还有其他的方法也可以传参数:

  • CallVoidMethodV可以获取一个数量可变的列表作为参数;
  • CallVoidMethodA可以获取一个union。

调用静态方法:

就是将第二步和第三步调用的方法改为对应的:

  • GetStaticMethodID获取对应的静态方法的ID
  • CallStaticIntMethod调用静态方法

调用静态方法应该使用对应的CallStaticTypeMethod, 其中的Type随着返回值类型不同而改变(参考前面非静态方法列出的类型)。

// 首先需要在Java中定义一个静态方法供JNI调用,如下所示。
public static void methodCalledByJni(String msgFromJni) {
    Log.d(TAG, "methodCalledByJni, msg: " + msgFromJni);
}
// 然后在JNI中调用上面定义的静态方法:
void callJavaMethod(JNIEnv *env, jobject thiz) {
    jclass clazz = env->FindClass("com/ryg/JniTestApp/MainActivity");
    if (clazz == NULL) {
      printf("find class MainActivity error! ");
      return;
    }
    jmethodID id = env->GetStaticMethodID(clazz, "methodCalledByJni",
    "(Ljava/lang/String; )V");
    if (id == NULL) {
      printf("find method methodCalledByJni error! ");
    }
    jstring msg = env->NewStringUTF("msg send by callJavaMethod in
    test.cpp.");
    env->CallStaticVoidMethod(clazz, id, msg);
}

4. 访问Java对象的属性

访问Java对象的属性和访问Java对象的方法基本上一样,只需要将函数里面的Method改为Field即可。

  • GetFieldID:获取某个属性的id
  • GetStaticFieldID:获取某个静态属性的id

5. 访问Java对象的Class对象

为了能够在C/C++中调用Java中的类,jni.h的头文件专门定义了jclass类型表示Java中Class类。JNIEnv中有3个函数可以获取jclass

  • jclass FindClass(const char* clsName):通过类的名称来获取jclass。注意,是类的全名,这时候包名不是用’".“点号而是用”/"来区分的。

    比如: jclass jcl_string=env->FindClass("java/lang/String");来获取Java中的String对象的class对象

  • jclass GetObjectClass(jobject obj):通过对象实例来获取jclass,相当于Java中的getClass()函数

  • jclass getSuperClass(jclass obj):通过jclass可以获取其父类的jclass对象


JNI 和 Java 层之间的数据传输

1 基本数据类型的传输

上层定义一个native的方法,需要一个int 参数 ,返回一个int值;JNI 对应上层的方法 , 打印出上层 传输下来的 int数据,并返回 int数据。

上层 收到 native 方法 返回的 值,在UI中显示出来

public native int getNumber(int num);
jint Java_XX_XX_XXActivity_getNumber(JNIEnv* env,jobject thiz,jint num)
{
    if(jniEnv == NULL) {
        jniEnv = env;
    }
    __android_log_print(ANDROID_LOG_INFO, "JNIMsg", "Java -- > C JNI : num = %d",num);
    return num*2;
}

注意:jintint的互转都可以直接使用强转,如:jint i = (jint) 10;

2 数组的传输

上层定义一个native的方法,需要一个int数组,返回一个int数组;JNI 对应上层的方法,取出上层传递数组中的数据处理和打印出来,并存入新数组中,最后把该数组返回给 Java层。

上层 收到 native返回的 数组,加工成字符串,在UI中显示出来

public native int[] getArrayNumber(int[] nums);
JNIEnv* jniEnv;
jintArray Java_XX_XX_XXActivity_getArrayNumber(JNIEnv* env,jobject thiz,jintArray nums)
{
    if(jniEnv == NULL) {
        jniEnv = env;
    }
    if(nums == NULL){
        return NULL;
    }
    jsize len = (*jniEnv)->GetArrayLength(jniEnv, nums);
    if(len <= 0) {
        return NULL;
    }
    jintArray array = (*jniEnv)->NewIntArray(jniEnv, len);
    if(array == NULL) {
        return NULL;
    }
    // 把 Java 传递下来的数组 用 jint* 存起来
    jint *body = (*env)->GetIntArrayElements(env, nums, 0);
    jint i = 0;
    jint num[len];
    for (; i < len; i++) {
        num[i] = body[i] * 2;
    }
    if(num == NULL){
        return NULL;
    }
	//(*env)->GetIntArrayRegion(env,array,start,len,buffer)
	// 从start开始复制长度为len 的数据到buffer中
    //给需要返回的数组赋值
    (*jniEnv)->SetIntArrayRegion(jniEnv,array, 0, len, num);
    return array;
}

对于其他类型数组,使用对应类型的成对方法读取和设置,如byte数组可使用 NewByteArray();SetByteArrayRegion();

3 引用数据类型

String 字符串传输

上层定义一个native的方法,需要一个String 参数,返回一个String;JNI对应上层的方法,打印出上层传输下来的String数据,并返回处理String数据。

上层 收到 native 方法 返回的 值,在UI中显示出来

public native String transferString(String mStrMSG);
jstring Java_XX_XX_XXActivity_transferString(JNIEnv* env,jobject thiz,jstring msg)
{
    if(jniEnv == NULL) {
        jniEnv = env;
    }
    char data[128];
    memset(data, 0, sizeof(data));
    char *c_msg = NULL;
    c_msg = (char *)(*jniEnv)->GetStringUTFChars(jniEnv, msg, 0);
    __android_log_print(ANDROID_LOG_INFO, "JNIMsg", "C JNI  ---- > %s",c_msg);
    return (*jniEnv)->NewStringUTF(jniEnv, "This is send by C JNI");
}
自定义对象的传输

自定义一个对象Person,上层定义一个native方法,参数Person,返回值Person;JNI接收对象,打印出相关信息数据,JNI 修改 Person 对象数据,并返回到上层。

上层接收到数据后 在UI显示出来

public native Object transferPerson(Person mPerson);     
public class Person {
    private String name;
    private int age;
    public Person() {
        name = "";
        age = 0;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }
}
extern JNIEnv* jniEnv;
jclass Person;
jobject mPerson;
jmethodID getName;
jmethodID setName;
jmethodID toString;
int InitPerson();
void ToString();
void GetName();
void SetName();
 
jobject Java_XX_XX_XXActivity_transferPerson(JNIEnv* env,jobject thiz,jobject person)
{
    if(jniEnv == NULL) {
        jniEnv = env;
    }
    if (Person == NULL || getName == NULL || setName == NULL || toString == NULL) {
        if (1 != InitPerson()) {
            return NULL;
        }
    }
    mPerson = person;
    if(mPerson == NULL) {
        return NULL;
    }
    GetName();
    GetAge();
    ToString();
    __android_log_print(ANDROID_LOG_INFO, "JNIMsg", "Begin Modify mPerson  .... ");
    SetName();
    SetAge();
    ToString();
    return mPerson;
}
 
int InitPerson() {
    if(jniEnv == NULL) {
        return 0;
    }
    if(Person == NULL) {
        Person = (*jniEnv)->FindClass(jniEnv,"com/XX/Person");
        if(Person == NULL){
            return -1;
        }
    }
    if (getName == NULL) {
        getName = (*jniEnv)->GetMethodID(jniEnv, Person, "getName","()Ljava/lang/String;");
        if (getName == NULL) {
            (*jniEnv)->DeleteLocalRef(jniEnv, Person);
            return -2;
        }
    }
    if (setName == NULL) {
        setName = (*jniEnv)->GetMethodID(jniEnv, Person, "setName","(Ljava/lang/String;)V");
        if (setName == NULL) {
            (*jniEnv)->DeleteLocalRef(jniEnv, Person);
            (*jniEnv)->DeleteLocalRef(jniEnv, getName);
            return -2;
        }
    }
    if (getAge == NULL) {
        getAge = (*jniEnv)->GetMethodID(jniEnv, Person, "getAge","()I");
        if (getAge == NULL) {
            (*jniEnv)->DeleteLocalRef(jniEnv, Person);
            (*jniEnv)->DeleteLocalRef(jniEnv, getName);
            (*jniEnv)->DeleteLocalRef(jniEnv, setName);
            return -2;
        }
    }
    if (setAge == NULL) {
        setAge = (*jniEnv)->GetMethodID(jniEnv, Person, "setAge","(I)V");
        if (setAge == NULL) {
            (*jniEnv)->DeleteLocalRef(jniEnv, Person);
            (*jniEnv)->DeleteLocalRef(jniEnv, getName);
            (*jniEnv)->DeleteLocalRef(jniEnv, setName);
            (*jniEnv)->DeleteLocalRef(jniEnv, getAge);
            return -2;
        }
    }
    if (toString == NULL) {
        toString = (*jniEnv)->GetMethodID(jniEnv, Person, "toString","()Ljava/lang/String;");
        if (toString == NULL) {
            (*jniEnv)->DeleteLocalRef(jniEnv, Person);
            (*jniEnv)->DeleteLocalRef(jniEnv, getName);
            (*jniEnv)->DeleteLocalRef(jniEnv, setName);
            (*jniEnv)->DeleteLocalRef(jniEnv, getAge);
            (*jniEnv)->DeleteLocalRef(jniEnv, setAge);
            return -2;
        }
    }
    return 1;
}
/**
* GetName  对应Person的getName方法
*/
void GetName() {
    if(Person == NULL || getName == NULL) {
        if(1 != InitPerson()){
            return;
        }
    }
    //调用方法
    jstring jstr = (*jniEnv)->CallObjectMethod(jniEnv, mPerson, getName);
    char* cstr = (char*) (*jniEnv)->GetStringUTFChars(jniEnv,jstr, 0);
    __android_log_print(ANDROID_LOG_INFO, "JNIMsg", "getName  ---- >  %s",cstr );
    //释放资源
    (*jniEnv)->ReleaseStringUTFChars(jniEnv, jstr, cstr);
    (*jniEnv)->DeleteLocalRef(jniEnv, jstr);
}
/**
* GetAge 对应Person的getName方法
*/
void GetAge() {
    if(Person == NULL || getName == NULL) {
        if(1 != InitPerson()){
            return;
        }
    }
    //调用方法
    jint age = (*jniEnv)->CallIntMethod(jniEnv, mPerson, getAge);
    __android_log_print(ANDROID_LOG_INFO, "JNIMsg", "getAge  ---- >  %d",age );
}
/**
* SetName 对应Person的setName方法
*/
void SetName() {
    if(Person == NULL || setName == NULL) {
        if(1 != InitPerson()){
            return;
        }
    }
    jstring jstr = (*jniEnv)->NewStringUTF(jniEnv, "Modify Name");
    //调用方法
    (*jniEnv)->CallVoidMethod(jniEnv, mPerson, setName,jstr);
    (*jniEnv)->DeleteLocalRef(jniEnv, jstr);
}
int age = 20;
/**
* SetAge 对应Person的setAge方法
*/
void SetAge() {
    if(Person == NULL || setAge == NULL) {
        if(1 != InitPerson()){
            return;
        }
    }
    //调用方法
    (*jniEnv)->CallVoidMethod(jniEnv, mPerson, setAge,age++);
}
/**
* ToString 对应 Person 的 toString 方法 , 打印出相关信息
*/
void ToString() {
    if(Person == NULL || toString == NULL) {
        if(1 != InitPerson()){
            return;
        }
    }
    jstring jstr = NULL;
    char* cstr = NULL;
    //调用方法
    jstr = (*jniEnv)->CallObjectMethod(jniEnv, mPerson, toString);
    cstr = (char*) (*jniEnv)->GetStringUTFChars(jniEnv,jstr, 0);
    __android_log_print(ANDROID_LOG_INFO, "JNIMsg", "C JNI toString  ---- >  %s",cstr );
    (*jniEnv)->ReleaseStringUTFChars(jniEnv, jstr, cstr);
    (*jniEnv)->DeleteLocalRef(jniEnv, jstr);
}

JNI 中创建 JAVA 对象的几种方式

1,使用函数NewObject可以用来创建JAVA对象;

GetMethodID 能够取得构造方法的jmethodID, 如果传入的要取得的方法名称设为""就能够取得构造方法。

构造方法的方法返回值类型的签名始终为void

例:

jclass clazz_date = env->FindClass("java/util/Date");
jmethodID mid_date = env->GetMethoID(clazz_date, "", "()V");
jobject now = env->NewObject(clazz_date, mid_date);

jmethodID mid _date_getTime = env->GetMethodID(clazz_date, "getTime" , "()")
jlong  time = env->CallLongMethod(now, mid_date_getTime);

2,使用函数AllocObject来创建JAVA对象

使用函数AllocObject可以根据传入的jclass创建一个JAVA对象,便是他的状态是非初始化的,在使用这个对象之前绝对要用CallNovirtualVoidMethod来调用该jclass的建构函数,这样可以延迟构造函数的调用,这一个部分用的很少,在这里只做简单的说明。

jclass clazz_str = env->FindClass("java/lang/String");
jmethodID methodID_str = env->GetMethodID(clazz_str ,"", "([C)V");
//预先创建一个没有初始化的字符串
jobject string = env->AllocObject(clazz_str);
//创建一个4个元素的的字符数组,然后以“清”,“原”,“卓”,“也”赋值
jcharArra  arg = env->NewCharArray(4);
env->SetCharArrayRegion(arg, 0, 4,L"清原卓也");
// 呼叫构建子
env->CallNovirtualVoidMethod(string ,clazz_str,methodID,arg);
jclass clazz_this = env->GetObjectClass(obj);
//这里假设这个对象的类有定义
static string  STATIC_STR;
jfieldID fieldID_str = env->GetStaticFieldID(clazz_this,"STATIC_STR","Ljava/lang/String");
env->SetStaticObjectField(clazz_str, fieldID_str, string);

3. JAVA字串 <-----> C/C++的字串

在JAVA中,使用的字符串String对象是Unicode(UTF-16)码,即每个字符不论是中文还是英文还是符号,一个字符总是占两个字节

JAVA通过JNI接口可以将JAVA的字符串转换到C/C++中的宽字符串(wchar_t*),或是传回一个 UTF-8的字符串(char *)到C/C++,反过来,C/C++可以通过一个宽字符串,或是一个UTF-8编码的字符串来创建一个JAVA端的String对象。

GetStringChars 和 GetStringUTFChars

这两个函数用来取得与某个jstring对象相关的JAVA字符串,分别可以取得UTF-16编码的宽字符串(jchar*)跟UTF8编码的字符串(char*

const jchar* GetStringChars(jstring str, jboolean* copied)
const char* GetStringUTFChars(jstring str, jboolean *copied)

第一个参数传入一个指向JAVA的String对象的jstring变量
第二个参数传入的是一个jboolean的指针

这两个函数分别都会有两个不同的动作:

  • 1,开辟新的内存,然后把JAVA中的String拷贝到这个内存中,然后返回指向这个内存地址的指针。
  • 2,直接返回指向JAVA的String的内存的指针,这个时候千万不要改变这个内存的内容,这将破坏String在Java中始终是常量这个原则

第二个参数是用来标示是否对Java的String对象进行了拷贝的。

如果传入的这个jboolean指针不是NULL,则他会给该指针所指向的内存传入JNI_TRUEJNI _FALSE标示是否进行了拷贝。

传入NULL表示不关心是否拷贝字符串,它就不会给jboolean*指向的内存赋值。

使用这两个函数取得的字符串,在不使用的时候,要使用ReleaseStringChars/ReleaseStringUTFChars来释放拷贝的内存,或是释放对JAVA的String对象的引用。

ReleaseStringChars(jstring jstr, const jchar* str)
ReleaseStringUTFChars(jstring jstr, const char* str)

第一个参数指定一个jstring 变量,即是要释放的本地字符串的来源
第二个参数就是要释放的本地字符串

GetStringCritical

为了增加直接传回指向JAVA字符串的指针的可能性(而不是拷贝),JDK1.2出来了新的函数GetStringCritical/ReleaseStringCritical

const jchar* GetStringCritical(jstring str, jboolean* copied);
void ReleaseStringCritical(jstring jstr, const jchar* str);

GetStringCritical/ReleaseStringCritical之间是一个关键区,在这关键区之中绝对不能呼叫JNI的其它函数和会造成当前线程中断或是会让当前线程等待的任何本地代码,否则将造成关键区代码执行期间垃圾回收器停止动作,任何触发垃圾回收器的线程也会暂停,其他的触发垃圾回收器的线程不能前进直到当前线程结束而激活垃圾回收器

在关键区千万不要出现中断操作,或是在JVM中分配任何新对象,否则会造成JVM死锁。

虽说这个函数会增加直接传回指向JAVA字符串的指针的可能性,不过还是会根据情况传回拷贝过的字符串

不支持GetStringUTFCritical,没有这样的函数,因为JAVA字符串用的是UTF-16,要转换成UTF-8编码的字符串始终需要进行一次拷贝,所以没有这样的函数。

GetStringRegion 和 GetStringUTFRegion

JAVA1.2F出来的函数,这个函数的动作,是把JAVA字符串的内容直接拷贝到C/C++的字符数组中,在呼叫这个函数之前必须有一个C/C++分配出来的字符串,然后传入到这个函数中进行字符串的拷贝。

由于C/C++分配内存开销相对小,而且JAVA中的String内容拷贝的开销可以忽略,更好的一点是些函数不会分配内存,不会抛出OutOfMemoryError异常

// 拷贝JAVA字符串并以UTF-8编码传入buffer
GetStringUTFRegion(jstring str , jsize start, jsize len, char* buffer);
// 拷贝JAVA字符串并以UTF-16编码传入buffer
GetStringRegion(jstring str , jsize start, jsize len, char* buffer);

其他的字符串函数:

jstring NewString(const jchar* str , jsize len);
jstring NewStringUTF(const char* str);
jsize GetStringLength(jstring  str);
jsize GetStringUTFLength(jstring str)

C/C++ 结构体和J ava对象的转换

直接参考该文:https://blog.csdn.net/tkwxty/article/details/103348031

但这种方法是一种非常简单暴力的方法,只适合特定的简单数据类型,如果是复杂的对象还是不能这样做。该方法可以作为一种拓展思路。这里就不拿出来整理了。

你可能感兴趣的:(JNI/NDK,android,JNI,NDK)