当前位置: 首页 > news >正文

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

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 intest.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;}@Overridepublic 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

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

相关文章:

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

JNI的数据类型和类型签名 数据类型 JNI的数据类型包含两种&#xff1a;基本类型和引用类型。 基本类型主要有jboolean、jchar、jint等&#xff0c;它们和Java中的数据类型的对应关系如下表所示。 JNI中的引用类型主要有类、对象和数组&#xff0c;它们和Java中的引用类型的对…...

EFLK与logstash过滤

目录 一、Filebeat工作原理&#xff1a; 二、为什么要使用Filebeat&#xff1a; 三、Filebeat和Logstash的区别&#xff1a; 四、logstash 的过滤插件&#xff1a; 五、FilebeatELK 部署&#xff1a; 1. 安装filebeat&#xff1a; 2. 设置 filebeat 的主配置文件&#xff1…...

docker jenkins

mkdir jenkins_home chown -R 1000:1000 /root/jenkins_home/docker run -d --name myjenkins -v /root/jenkins_home:/var/jenkins_home -p 8080:8080 -p 50000:50000 --restarton-failure jenkins/jenkins:lts-jdk17参考 Official Jenkins Docker imageDocker 搭建 Jenkins …...

单例模式之「双重校验锁」

单例模式之「双重校验锁」 单例模式 单例即单实例&#xff0c;只实例出来一个对象。一般在创建一些管理器类、工具类的时候&#xff0c;需要用到单例模式&#xff0c;比如JDBCUtil 类&#xff0c;我们只需要一个实例即可&#xff08;多个实例也可以实现功能&#xff0c;但是增…...

2023年中国商业版服务器操作系统市场发展规模分析:未来将保持稳定增长[图]

服务器操作系统一般指的是安装在大型计算机上的操作系统&#xff0c;比如Web服务器、应用服务器和数据库服务器等&#xff0c;是企业IT系统的基础架构平台&#xff0c;也是按应用领域划分的三类操作系统之一。同时服务器操作系统也可以安装在个人电脑上。 服务器操作系统分类 …...

BIM如何通过3D开发工具HOOPS实现WEB轻量化?

随着建筑行业的数字化转型和信息建模技术的不断发展&#xff0c;建筑信息模型&#xff08;BIM&#xff09;已经成为设计、建造和管理建筑项目的标准。然而&#xff0c;BIM模型通常包含大量的数据&#xff0c;导致在Web上的传输和查看效率低下。为了解决这一挑战&#xff0c;HOO…...

Unity 3D基础——通过四元数控制对象旋转

在这个例子中&#xff0c;通过键盘的左右方向来控制场景中的球体 Sphere 的横向运动&#xff0c;而 Cube 立方体则会一直朝着球体旋转。 1.在场景中新建一个 Cube 立方体和一个 Sphere 球体&#xff0c;在 Inspector 视图中设置 Cube 立方体的坐标为&#xff08;3&#xff0c;0…...

python--短路运算,把0、空字符串和None看成 False,其他数值和非空字符串都看成 True

代码 print(3 and 4 and 5) # 5 print(5 and 6 or 7) # 6 4 > 3 and print(‘hello world’) # 输出hello world 注释&#xff1a; 在逻辑运算中&#xff0c;不一定逻辑运算符的两边都是纯表达式。也可以是数值类型的数据。 Python把0、空字符串和None看成 False&#xff…...

《算法通关村第一关——链表青铜挑战笔记》

《算法通关村第一关——链表青铜挑战笔记》 Java如何构造出链表 概念 如何构造出链表&#xff0c;首先必须了解什么是链表&#xff01; 单向链表就像一个铁链一样&#xff0c;元素之间相互链接&#xff0c;包含多个节点&#xff0c;每个节点有一个指向后继元素的next指针。…...

【深度学习实验】循环神经网络(四):基于 LSTM 的语言模型训练

目录 一、实验介绍 二、实验环境 1. 配置虚拟环境 2. 库版本介绍 三、实验内容 0. 导入必要的工具包 1. RNN与梯度裁剪 2. LSTM模型 3. 训练函数 a. train_epoch b. train 4. 文本预测 5. GPU判断函数 6. 训练与测试 7. 代码整合 经验是智慧之父&#xff0c;记忆…...

IOS课程笔记[1-3] 第一个IOS应用

安装开发环境 安装Xcode软件 历史版本查找 https://developer.apple.com/download/all/?qdebug 创建Object-C项目 启动过程 步骤 1.加载Main中定义的storyBoard 2.加载Main控制器 3.加载控制器下的View组件显示 获取控件的两种方式 定义属性连线&#xff1a;property (…...

Flink的基于两阶段提交协议的事务数据汇实现

背景 在flink中可以通过使用事务性数据汇实现精准一次的保证&#xff0c;本文基于Kakfa的事务处理来看一下在Flink 内部如何实现基于两阶段提交协议的事务性数据汇. flink kafka事务性数据汇的实现 1。首先在开始进行快照的时候也就是收到checkpoint通知的时候&#xff0c;在…...

树模型(三)决策树

决策树是什么&#xff1f;决策树(decision tree)是一种基本的分类与回归方法。 长方形代表判断模块 (decision block)&#xff0c;椭圆形成代表终止模块(terminating block)&#xff0c;表示已经得出结论&#xff0c;可以终止运行。从判断模块引出的左右箭头称作为分支(branch)…...

vueday01——使用属性绑定+ref属性定位获取id

1.属性绑定&#xff08;Attribute 绑定&#xff09; 第一种写法 <div v-bind:id"refValue"> content </div> 第二种写法&#xff08;省略掉v-bind&#xff09; <div :id"refValue"> content </div> 2.代码展示 <template…...

LeetCode 260. 只出现一次的数字 III:异或

【LetMeFly】260.只出现一次的数字 III 力扣题目链接&#xff1a;https://leetcode.cn/problems/single-number-iii/ 给你一个整数数组 nums&#xff0c;其中恰好有两个元素只出现一次&#xff0c;其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返…...

使用PyTorch解决多分类问题:构建、训练和评估深度学习模型

&#x1f497;&#x1f497;&#x1f497;欢迎来到我的博客&#xff0c;你将找到有关如何使用技术解决问题的文章&#xff0c;也会找到某个技术的学习路线。无论你是何种职业&#xff0c;我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章&#xff0c;也欢…...

基于nodejs+vue网课学习平台

各功能简要描述如下: 1个人信息管理:包括对学生用户、老师和管理员的信息进行录入、修改&#xff0c;以及老师信息的审核等 2在库课程查询:用于学生用户查询相关课程的功能 3在库老师查询:用于学生用户查询相关老师教学的所有课程的功能。 4在库学校查询:用于学生用户查询相关学…...

读书笔记:Effective C++ 2.0 版,条款13(初始化顺序==声明顺序)、条款14(基类有虚析构)

条款13: 初始化列表中成员列出的顺序和它们在类中声明的顺序相同 类成员是按照它们在类里被声明的顺序进行初始化的&#xff0c;和它们在成员初始化列表中列出的顺序没一点关系。 根本原因可能是考虑到内存的分布&#xff0c;按照定义顺序进行排列。 另外&#xff0c;初始化列表…...

flutter开发实战-下拉刷新与上拉加载更多实现

flutter开发实战-下拉刷新与上拉加载更多实现 在开发中经常遇到列表需要下拉刷新与上拉加载更多&#xff0c;这里使用EasyRefresh&#xff0c;版本是3.3.21 一、什么是EasyRefresh EasyRefresh可以在Flutter应用程序上轻松实现下拉刷新和上拉加载。它几乎支持所有Flutter Sc…...

旧手机热点机改造成服务器方案

如果你也跟我一样有这种想法, 那真的太酷了!!! ok,前提是得有root,不然体验大打折扣 目录 目录 1.做一个能爬墙能走百度直连的热点机(做热点机用) 2.做emby视频服务器 3.做文件服务, 存取文件 4.装青龙面板,跑一些定时任务 5.做远程摄像头监控 6.做web服务器 7.内网穿…...

遍历 Map 类型集合的方法汇总

1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

【Linux】C语言执行shell指令

在C语言中执行Shell指令 在C语言中&#xff0c;有几种方法可以执行Shell指令&#xff1a; 1. 使用system()函数 这是最简单的方法&#xff0c;包含在stdlib.h头文件中&#xff1a; #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...

【论文笔记】若干矿井粉尘检测算法概述

总的来说&#xff0c;传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度&#xff0c;通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...

C# 类和继承(抽象类)

抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

三体问题详解

从物理学角度&#xff0c;三体问题之所以不稳定&#xff0c;是因为三个天体在万有引力作用下相互作用&#xff0c;形成一个非线性耦合系统。我们可以从牛顿经典力学出发&#xff0c;列出具体的运动方程&#xff0c;并说明为何这个系统本质上是混沌的&#xff0c;无法得到一般解…...

Map相关知识

数据结构 二叉树 二叉树&#xff0c;顾名思义&#xff0c;每个节点最多有两个“叉”&#xff0c;也就是两个子节点&#xff0c;分别是左子 节点和右子节点。不过&#xff0c;二叉树并不要求每个节点都有两个子节点&#xff0c;有的节点只 有左子节点&#xff0c;有的节点只有…...

laravel8+vue3.0+element-plus搭建方法

创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

push [特殊字符] present

push &#x1f19a; present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中&#xff0c;push 和 present 是两种不同的视图控制器切换方式&#xff0c;它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...

【前端异常】JavaScript错误处理:分析 Uncaught (in promise) error

在前端开发中&#xff0c;JavaScript 异常是不可避免的。随着现代前端应用越来越多地使用异步操作&#xff08;如 Promise、async/await 等&#xff09;&#xff0c;开发者常常会遇到 Uncaught (in promise) error 错误。这个错误是由于未正确处理 Promise 的拒绝&#xff08;r…...