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

news2024/12/25 1:39:16

JNI的数据类型和类型签名

数据类型

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

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

在这里插入图片描述

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

在这里插入图片描述

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

typedef jint jsize;

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

类型签名

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

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

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

在这里插入图片描述

从上表可以看出,基本数据类型的签名是有规律的,一般为首字母的大写,但是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等对应的数组。

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

数组类型获取函数释放函数
booleanGetBooleanArrayElementsReleaseBooleanArrayElements
byteGetByteArrayElementsReleaseByteArrayElements
charGetCharArrayElementsReleaseCharArrayElements
shortGetShortArrayElementsReleaseShortArrayElements
intGetIntArrayElementsReleaseIntArrayElements
longGetLongArrayElementsReleaseLongArrayElements
floatGetFloatArrayElementsReleaseFloatArrayElements
doubleGetDoubleArrayElementsReleaseDoubleArrayElements

  • 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, 如果传入的要取得的方法名称设为"<init>"就能够取得构造方法。

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

例:

jclass clazz_date = env->FindClass("java/util/Date");
jmethodID mid_date = env->GetMethoID(clazz_date, "<init>", "()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 ,"<init>", "([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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1098148.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ICC2:如何抓取“no net“的shape和via

我正在「拾陆楼」和朋友们讨论有趣的话题&#xff0c;你⼀起来吧&#xff1f; 拾陆楼知识星球入口 pr过程中(尤其是eco)会产生一些no net的shape或via&#xff0c;它们会造成drc和lvs问题&#xff0c;但是常规的办法无法把他们抓出来&#xff0c;下面分享可以获取no net的方法…

数字化时代下,汽车行业如何突破现有营销困境?

之前三年的“口罩”时期&#xff0c;给全球和中国汽车市场带来不小影响&#xff0c;汽车销售市场整体下滑&#xff0c;传统营销模式很难适应现阶段汽车营销需求&#xff0c;那么在当下&#xff0c;汽车行业应该如何突破现有营销困境呢&#xff1f;接下来就由媒介盒子跟大家聊聊…

如何同步 Github 和 Gitee的仓库代码

一、从github导入仓库&#xff0c;手动同步 在 Gitee 的项目主页&#xff0c;导入的仓库会会有一个同步的按钮&#xff0c;你只用点一下&#xff0c;即可与 Github 同步更新&#xff0c;但是注意这里的同步功能默认是强制同步。有点麻烦的是&#xff0c;我们需要在推送到 Githu…

推理成本增加10倍?对文心大模型4.0的一些猜想

夕小瑶科技说 原创 作者 | 卖萌酱 大家好&#xff0c;我是卖萌酱。 相信不少小伙伴这几天都听到了消息&#xff0c;在期待下周即将发布的文心大模型4.0。我们的几个读者群里也发生了相关的讨论&#xff1a; 讨论的核心主要围绕以下两个话题展开&#xff1a; 文心4.0能不能打过…

git操作说明

SourceURL:file:///home/kingqi/桌面/git操作说明.doc 本地建立仓库 mkdir namebao cd namebao pwd git init 初始化 cd .git/ gedit config 本地存储 进入目录上传全部文件 git add . 提交 git commit -m “说明” 远程提交 复制gitee或者github仓库链接 可以直…

前端开发tips

vue配置启动项目自动打开浏览器 打开package.json找到启动命令npm run dev 跟npm run serve(这两种命令都可以) 后面增加 --open Vue项目设置路径src目录别名为 Vue2 编辑vue.config.js内容如下&#xff1a; const { defineConfig } require(vue/cli-service)const path…

使用Cython对Python进行提速优化

因为常常使用Python&#xff0c;经常能感觉到和C&#xff0c;Java来说Python的速度太慢了。其中很大程度上是因为Python的类型是动态的&#xff0c;在解释类型方面花了较长时间。在调研过程中&#xff0c;发现给Python提速一个比较可行的方案是用Cython改写。 Cython的原理&am…

零售数据分析师熬夜整理:人、货、场、供、财这样做

在零售数据分析中&#xff0c;人、货、场、供、财数据分析非常重要&#xff0c;它们分别是指人员、商品、场所、供应和财务&#xff0c;对这些要素进行数据分析&#xff0c;可以更好地了解市场需求、优化商品供应链、调整销售策略和提高盈利能力。零售数据量大、分析指标多且复…

蓝桥·算法双周赛

文章目录 三带一数树数分组健身契合匹配奇怪的线段 一、三带一 本题思路:本题看了数据范围可以直接暴力解决。 #include <bits/stdc.h>int main() {std::ios::sync_with_stdio(false);std::cin.tie(nullptr);std::cout.tie(nullptr);int T;std::cin>>T;while(T-…

C#网络爬虫实例:使用RestSharp获取Reddit首页的JSON数据并解析

Reddit 是一个非常受欢迎的分享社交新闻聚合网站&#xff0c;用户可以在上面发布和内容。我们的目标是抓取 Reddit 首页的数据 JSON&#xff0c;以便进一步分析和使用。 C#技术概述&#xff1a;C#是一种流行的编程语言&#xff0c;它具有流畅流畅的特点&#xff0c;非常适合开发…

用手势识别来测试视力?试试用百度AI来实现想法

文章目录 ⭐ 前言⭐ 灵感来源⭐ 项目准备⭐ 项目实现⭐ 不足与展望 ⭐ 前言 10月17日&#xff0c;以“生成未来&#xff08;PROMPT THE WORLD&#xff09;”为主题的百度世界2023将在北京首钢园举办。百度创始人、董事长兼首席执行官李彦宏将带来以“手把手教你做AI原生应用”…

【微信小程序开发】基础语法篇

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于小程序的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 一.视图层 1.数据绑定 wxml js 2 .列…

android CountDownTimer倒计时随时随地开启或关闭

前言 业务上有个需求&#xff0c;在心跳被触发时需要记录一个时间 当心跳断开后120s需要断开连接 如果收到心跳包就重置这个定时器的时间 代码实现 private CountDownTimer mTimer; private boolean mIsTimerRunning false;if (!mIsTimerRunning) {// 启动定时器startTimer…

消息队列学习分享

消息队列学习 消息队列来解决问题 &#xff08;1&#xff09;异步处理 消息通知、日志管理、更新统计数据等步骤 &#xff08;2&#xff09;流量控制 如何避免过多的请求压垮我们的系统&#xff1f; 比如一个秒杀系统&#xff0c;网关在收到请求后&#xff0c;将请求放入…

基于Pytest+Requests+Allure实现接口自动化测试

一、整体结构 框架组成&#xff1a;pytestrequestsallure设计模式&#xff1a; 关键字驱动项目结构&#xff1a; 工具层&#xff1a;api_keyword/参数层&#xff1a;params/用例层&#xff1a;case/数据驱动&#xff1a;data_driver/数据层&#xff1a;data/逻辑层&#xff1a…

数字化时代,数据仓库究竟是干什么的?

无论你是否专门从事大数据开发&#xff0c;作为一个开发人员&#xff0c;应该都听说过数据仓库的概念&#xff0c;那你知道为什么会出现数据仓库&#xff1f;数据仓库究竟是干嘛的吗&#xff1f;有什么价值和意义呢&#xff1f;那么本文就带到入门&#xff0c;揭开数据仓库的面…

嵌入式软硬分工与职业发展

嵌入式软硬分工与职业发展&#xff1a; 嵌入式系统分为软件和硬件两个方向。大公司通常明确员工从事嵌入式软件或硬件工作&#xff0c;分工合理利用经验解决问题。小公司可能综合工作&#xff0c;但长期不利深入学习和发展&#xff0c;对个人竞争力不利。嵌入式软件一般指底层…

财报解读:百事业绩继续超预期,无糖布局将带来新爆发?

7月份以来&#xff0c;美国市场通胀“二次反弹”&#xff0c;叠加新型减肥药在北美市场持续风靡&#xff0c;外界普遍预期食品饮料品类的消费将受到冲击。 然而&#xff0c;在此环境下&#xff0c;行业巨头百事仍然交出了一份全面超预期的三季报&#xff0c;并且还有力回应了新…

node 通过axios发送post请求(FormData)

方案一&#xff1a; const axios require(axios) const FormData require(form-data) const fs require(fs)const sdUpscaleOnAzure async (req, res) > {const data new FormData()data.append(image, fs.readFileSync(/temp/ai/sd/download/1.png))let config {hea…

动态规划:10 0-1背包理论基础II(滚动数组)

动态规划&#xff1a;10 0-1背包理论基础II&#xff08;滚动数组&#xff09; 接下来还是用如下这个例子来进行讲解 背包最大重量为4。 物品为&#xff1a; 重量价值物品0115物品1320物品2430 问背包能背的物品最大价值是多少&#xff1f; 一维dp数组&#xff08;滚动数组…