当前位置: 首页 > 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.内网穿…...

网工实验笔记:策略路由PBR的应用场景

一、概述 PBR&#xff08;Policy-Based Routing&#xff0c;策略路由&#xff09;&#xff1a;PBR使得网络设备不仅能够基于报文的目的IP地址进行数据转发&#xff0c;更能基于其他元素进行数据转发&#xff0c;例如源IP地址、源MAC地址、目的MAC地址、源端口号、目的端口号、…...

webrtc快速入门——使用 WebRTC 拍摄静止的照片

文章目录 使用 getUserMedia() 拍摄静态照片HTML 标记JavaScript 代码初始化startup() 函数获取元素引用获取流媒体 监听视频开始播放处理按钮上的点击包装 startup() 方法 清理照片框从流中捕获帧 例子代码HTML代码CSS代码JavaScript代码 过滤器使用特定设备 使用 getUserMedi…...

预约按摩app软件开发定制足浴SPA上们服务小程序

同城按摩小程序是一种基于地理位置服务的小程序&#xff0c;它可以帮助用户快速找到附近的按摩师&#xff0c;并提供在线预约、评价、支付等功能。用户可以通过手机或者其他移动设备访问同城按摩小程序&#xff0c;实现足不出户就能预约到专业的按摩服务。 一、同城按摩小程序的…...

jenkins出错与恢复

如果你的jenkins出现了如下图所示问题&#xff08;比如不能下载插件&#xff0c;无法保存任务等&#xff09;&#xff0c;这个时候就需要重新安装了。 一、卸载干净jenknis 要彻底卸载 Jenkins&#xff0c;您可以按照以下步骤进行操作&#xff1a; 1、停止 Jenkins 服务&…...

ssh免密登录的原理RSA非对称加密的理解

RSA非对称加密&#xff0c;是采用公钥加密私钥解密的原则。 举个例子SSH的免密登录 SSH免密登录是通过使用公钥加密技术实现的。以下是SSH免密登录的原理&#xff1a; 1. 生成密钥对&#xff1a;首先&#xff0c;在客户端上生成一对密钥&#xff0c;包括一个私钥和一个公钥。私…...

【监督学习】基于合取子句进化算法(CCEA)和析取范式进化算法(DNFEA)解决分类问题(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

力扣每日一题41:缺失的第一个正数

题目描述&#xff1a; 给你一个未排序的整数数组 nums &#xff0c;请你找出其中没有出现的最小的正整数。 请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,0] 输出&#xff1a;3示例 2&#xff1a; 输…...

OpenCV与mediapipe实践

1. 安装前准备 开发环境&#xff1a;vscode venv 设置vscode, 建立项目&#xff0c;如: t1/src, 用vscode打开&#xff0c;新建终端Terminal&#xff0c;这时可能会有错误产生&#xff0c;解决办法&#xff1a; 运行命令&#xff1a;Set-ExecutionPolicy -ExecutionPolicy …...

【css拾遗】粘性布局实现有滚动条的情况下,按钮固定在页面底部展示

效果&#xff1a; 滚动条滚动过程中&#xff0c;按钮的位置位于手机的底部 滚动条滚到底部时&#xff0c;按钮的位置正常 这个position:sticky真的好用&#xff0c;我原先的想法是利用滚动条滚动事件去控制&#xff0c;没想到css就可以解决 <template><view class…...

git 创建并配置 GitHub 连接密钥

前记&#xff1a; git svn sourcetree gitee github gitlab gitblit gitbucket gitolite gogs 版本控制 | 仓库管理 ---- 系列工程笔记. Platform&#xff1a;Windows 10 Git version&#xff1a;git version 2.32.0.windows.1 Function&#xff1a; git 创建并配置 GitHub…...

学做ppt的网站有哪些内容/seo推广系统

hive空字符串数组和空数组 最近在处理数据时发现一个有意思的情况 空字符串数组 &#xff1a;array(’’) 空数组&#xff1a;array() select size(array()), size(array()); 将字符串数组转换为字符串&#xff1a; concat_ws(,,collect_set(cast(colum))) 如果想查找表中…...

广州网站的优化/网站快速收录软件

笔者是运维工程师&#xff0c;对Linux方面有点心得&#xff0c;现在说一下需要掌握哪方面的工具吧。说到工具&#xff0c;在行外可以说是技能&#xff0c;在行内我们一般称之为工具&#xff0c;就是运维必须要掌握的工具。我就大概列出这几方面&#xff0c;这样入门就基本没有问…...

怎么样在网站做产品推广/佛山seo技术

导读 BesLyric 可以将 ncm格式转MP3 了&#xff01; 前几天有网友到我的博客下评论说现在会员才能下载下来的音乐发现后缀是 ncm&#xff0c; 没法使用 Beslyric 来制作歌词&#xff0c;昨天升级了一下软件&#xff0c;将 ncm 文件在软件内 “转” 成mp3, 现在软件可以直接选…...

自己建个网站怎么挣钱/营销推广技巧

Python的变量不用声明&#xff0c;赋值之后就可以直接使用&#xff0c;类型是在运行过程中自动确定的&#xff0c;这就是动态类型模型。该模型把变量和对象设计成两个不同的实体&#xff0c;对象是存储数据的地方&#xff0c;对象的类型是由初始值自动决定的&#xff0c;而变量…...

潜江资讯网全部/seo优化教程自学

什么是移动WEB开发&#xff0c;我个人理解就是&#xff0c;将网页更好的显示在移动端的一些设置&#xff0c;简单来说就两点如下&#xff1a; 1、流式布局&#xff0c;即百分比自适应布局 将body下的div容器的样式设置如下&#xff1a; div{ width:100%; } 2、viewport视口 在h…...

做网站英语老师的简历/seo搜索引擎官网

...