JNI 的数据类型以及和Java层之间的数据转换
JNI的数据类型和类型签名
数据类型
JNI的数据类型包含两种:基本类型和引用类型。
基本类型主要有jboolean、jchar、jint等,它们和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方法将传进来的prompt(jstring类型)转换成为UTF-8的格式,就能够在本地方法中使用了。
注意:在使用完你所转换之后的对象之后,需要显示调用ReleaseStringUTFChars方法,让JVM释放转换成UTF-8的string的对象的空间,如果不显示的调用的话,JVM中会一直保存该对象,不会被垃圾回收器回收,因此就会导致内存溢出。
下面是访问String的一些方法:
GetStringUTFChars将jstring转换成为UTF-8格式的char*GetStringChars将jstring转换成为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 |
GetObjectArrayElementreturns the object element at a given index.SetObjectArrayElementupdates 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。其参数的意义:
env:JNIEnvcls:第一步获取的jclass"callback":要调用的方法名"(I)V":方法的Signature
③ 调用方法:
(*env)->CallVoidMethod(env, obj, mid, depth);
使用CallVoidMethod方法调用方法。参数的意义:
env:JNIEnv指针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获取对应的静态方法的IDCallStaticIntMethod调用静态方法
调用静态方法应该使用对应的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:获取某个属性的idGetStaticFieldID:获取某个静态属性的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;
}
注意:jint与int的互转都可以直接使用强转,如: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_TRUE 或 JNI _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的数据类型包含两种:基本类型和引用类型。 基本类型主要有jboolean、jchar、jint等,它们和Java中的数据类型的对应关系如下表所示。 JNI中的引用类型主要有类、对象和数组,它们和Java中的引用类型的对…...
EFLK与logstash过滤
目录 一、Filebeat工作原理: 二、为什么要使用Filebeat: 三、Filebeat和Logstash的区别: 四、logstash 的过滤插件: 五、FilebeatELK 部署: 1. 安装filebeat: 2. 设置 filebeat 的主配置文件࿱…...
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 …...
单例模式之「双重校验锁」
单例模式之「双重校验锁」 单例模式 单例即单实例,只实例出来一个对象。一般在创建一些管理器类、工具类的时候,需要用到单例模式,比如JDBCUtil 类,我们只需要一个实例即可(多个实例也可以实现功能,但是增…...
2023年中国商业版服务器操作系统市场发展规模分析:未来将保持稳定增长[图]
服务器操作系统一般指的是安装在大型计算机上的操作系统,比如Web服务器、应用服务器和数据库服务器等,是企业IT系统的基础架构平台,也是按应用领域划分的三类操作系统之一。同时服务器操作系统也可以安装在个人电脑上。 服务器操作系统分类 …...
BIM如何通过3D开发工具HOOPS实现WEB轻量化?
随着建筑行业的数字化转型和信息建模技术的不断发展,建筑信息模型(BIM)已经成为设计、建造和管理建筑项目的标准。然而,BIM模型通常包含大量的数据,导致在Web上的传输和查看效率低下。为了解决这一挑战,HOO…...
Unity 3D基础——通过四元数控制对象旋转
在这个例子中,通过键盘的左右方向来控制场景中的球体 Sphere 的横向运动,而 Cube 立方体则会一直朝着球体旋转。 1.在场景中新建一个 Cube 立方体和一个 Sphere 球体,在 Inspector 视图中设置 Cube 立方体的坐标为(3,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 注释: 在逻辑运算中,不一定逻辑运算符的两边都是纯表达式。也可以是数值类型的数据。 Python把0、空字符串和None看成 Falseÿ…...
《算法通关村第一关——链表青铜挑战笔记》
《算法通关村第一关——链表青铜挑战笔记》 Java如何构造出链表 概念 如何构造出链表,首先必须了解什么是链表! 单向链表就像一个铁链一样,元素之间相互链接,包含多个节点,每个节点有一个指向后继元素的next指针。…...
【深度学习实验】循环神经网络(四):基于 LSTM 的语言模型训练
目录 一、实验介绍 二、实验环境 1. 配置虚拟环境 2. 库版本介绍 三、实验内容 0. 导入必要的工具包 1. RNN与梯度裁剪 2. LSTM模型 3. 训练函数 a. train_epoch b. train 4. 文本预测 5. GPU判断函数 6. 训练与测试 7. 代码整合 经验是智慧之父,记忆…...
IOS课程笔记[1-3] 第一个IOS应用
安装开发环境 安装Xcode软件 历史版本查找 https://developer.apple.com/download/all/?qdebug 创建Object-C项目 启动过程 步骤 1.加载Main中定义的storyBoard 2.加载Main控制器 3.加载控制器下的View组件显示 获取控件的两种方式 定义属性连线:property (…...
Flink的基于两阶段提交协议的事务数据汇实现
背景 在flink中可以通过使用事务性数据汇实现精准一次的保证,本文基于Kakfa的事务处理来看一下在Flink 内部如何实现基于两阶段提交协议的事务性数据汇. flink kafka事务性数据汇的实现 1。首先在开始进行快照的时候也就是收到checkpoint通知的时候,在…...
树模型(三)决策树
决策树是什么?决策树(decision tree)是一种基本的分类与回归方法。 长方形代表判断模块 (decision block),椭圆形成代表终止模块(terminating block),表示已经得出结论,可以终止运行。从判断模块引出的左右箭头称作为分支(branch)…...
vueday01——使用属性绑定+ref属性定位获取id
1.属性绑定(Attribute 绑定) 第一种写法 <div v-bind:id"refValue"> content </div> 第二种写法(省略掉v-bind) <div :id"refValue"> content </div> 2.代码展示 <template…...
LeetCode 260. 只出现一次的数字 III:异或
【LetMeFly】260.只出现一次的数字 III 力扣题目链接:https://leetcode.cn/problems/single-number-iii/ 给你一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返…...
使用PyTorch解决多分类问题:构建、训练和评估深度学习模型
💗💗💗欢迎来到我的博客,你将找到有关如何使用技术解决问题的文章,也会找到某个技术的学习路线。无论你是何种职业,我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章,也欢…...
基于nodejs+vue网课学习平台
各功能简要描述如下: 1个人信息管理:包括对学生用户、老师和管理员的信息进行录入、修改,以及老师信息的审核等 2在库课程查询:用于学生用户查询相关课程的功能 3在库老师查询:用于学生用户查询相关老师教学的所有课程的功能。 4在库学校查询:用于学生用户查询相关学…...
读书笔记:Effective C++ 2.0 版,条款13(初始化顺序==声明顺序)、条款14(基类有虚析构)
条款13: 初始化列表中成员列出的顺序和它们在类中声明的顺序相同 类成员是按照它们在类里被声明的顺序进行初始化的,和它们在成员初始化列表中列出的顺序没一点关系。 根本原因可能是考虑到内存的分布,按照定义顺序进行排列。 另外,初始化列表…...
flutter开发实战-下拉刷新与上拉加载更多实现
flutter开发实战-下拉刷新与上拉加载更多实现 在开发中经常遇到列表需要下拉刷新与上拉加载更多,这里使用EasyRefresh,版本是3.3.21 一、什么是EasyRefresh EasyRefresh可以在Flutter应用程序上轻松实现下拉刷新和上拉加载。它几乎支持所有Flutter Sc…...
旧手机热点机改造成服务器方案
如果你也跟我一样有这种想法, 那真的太酷了!!! ok,前提是得有root,不然体验大打折扣 目录 目录 1.做一个能爬墙能走百度直连的热点机(做热点机用) 2.做emby视频服务器 3.做文件服务, 存取文件 4.装青龙面板,跑一些定时任务 5.做远程摄像头监控 6.做web服务器 7.内网穿…...
【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...
相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...
CentOS下的分布式内存计算Spark环境部署
一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架,相比 MapReduce 具有以下核心优势: 内存计算:数据可常驻内存,迭代计算性能提升 10-100 倍(文档段落:3-79…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...
Swagger和OpenApi的前世今生
Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章,二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑: 🔄 一、起源与初创期:Swagger的诞生(2010-2014) 核心…...
人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式
今天是关于AI如何在教学中增强学生的学习体验,我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育,这并非炒作,而是已经发生的巨大变革。教育机构和教育者不能忽视它,试图简单地禁止学生使…...
【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
篇章二 论坛系统——系统设计
目录 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 1. 数据库设计 1.1 数据库名: forum db 1.2 表的设计 1.3 编写SQL 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 通过需求分析获得概念类并结合业务实现过程中的技术需要&#x…...
CTF show 数学不及格
拿到题目先查一下壳,看一下信息 发现是一个ELF文件,64位的 用IDA Pro 64 打开这个文件 然后点击F5进行伪代码转换 可以看到有五个if判断,第一个argc ! 5这个判断并没有起太大作用,主要是下面四个if判断 根据题目…...
