JNI编程指南(一):基本类型、字符串、数组

前言

对于任何一个初学者,学习JNI都是从Java和C/C++之间如何传递数据,以及数据类型之间是如何相互映射开始。

Native方法和C函数原型

看点代码

package com.net168.xxx
class Simple {
    private native String testA(String str);
    private native static void testA(int num);
}

//C端源码
JNIEXPORT jstring JNICALL
    Java_Com_net168_xxx_Simple_testA(JNIEnv *env, jobject thiz, jstring str);
JNIEXPORT void JNICALL
    Java_Com_net168_xxx_Simple_testB(JNIEnv *env, jclass clz, jint num);

知识点

  • C函数方法格式:JNIEXPORT 返回类型 JNICALL Java_包名_方法名(JNIEnv *env, jobect/jclass thiz, 入参列表)
  • 本地方法存在重载情况时,会有双下划线"__",后面跟着参数描述符,也就是长函数名;VM连接优先链接短函数名,然后链接长函数名,如果存在两个重载的本地方法,则只会链接长函数名。
  • 链接函数还可以通过JNI的RegisterNatives来注册与一个类关联的本地方法。
  • JNIEXPORTJNICALL是定义在jni.h里面的两个宏,用来确保函数在本地库外可见,C编译时会进行正确转换。
  • JNIEnv是一个接口指针,指向若干个函数表,提供了JNI函数帮助C函数访问JVM里面的数据结构。
  • 本地方法是静态方法时,C函数的第二个变量是jclass,代表本地方法所在的类;如果是一个实例方法时,其变量的类型是jobject,代表本地方法所在的对象实例。

类型映射

本地方法声明中的参数类型在C语言中都有对应的类型,具体对应表格如下:

java类型 本地类型 描述
boolean jboolean C/C++8位整型
byte jbyte C/C++带符号的8位整型
char jchar C/C++无符号的16位整型
short jshort C/C++带符号的16位整型
int jint C/C++带符号的32位整型
long jlong C/C++带符号的64位整型e
float jfloat C/C++32位浮点型
double jdouble C/C++64位浮点型
Object jobject 任何Java对象,或者没有对应java类型的对象
Class jclass Class对象
String jstring 字符串对象
Object[] jobjectArray 任何对象的数组
boolean[] jbooleanArray 布尔型数组
byte[] jbyteArray 比特型数组
char[] jcharArray 字符型数组
short[] jshortArray 短整型数组
int[] jintArray 整型数组
long[] jlongArray 长整型数组
float[] jfloatArray 浮点型数组
double[] jdoubleArray 双浮点型数组

知识点

  • Java里面有两种类型:基本类型和引用类型,JNI对这两个类型的处理方式是不同的。
  • JNI把Java中的对象当做一个C指针传递到本地方法中,这个指针指向JVM的内部数据结构,也就是其在内存中的储存方式是不可见的,必须通过JNI函数来操作JVM中的对象。

字符串处理

jstring转c语言字符串

JNIEXPORT jstring JNICALL
    Java_Com_net168_xxx_Simple_testA(JNIEnv *env, jobject thiz, jstring jstr)
{
    jboolean isCopy;
    //获取utf-8格式的c字符串
    const char *str1 = env->GetStringUTFChars(jstr, &isCopy);
    //do something
    env->ReleaseStringUTFChars(jstr, str1);

    //获取Unicode格式的c字符串
    const jchar *str2 = env->GetStringChars(jstr, &isCopy);
    //do something
    env->ReleaseStringChars(jstr, str2);
}

知识点

  • GetStringUTFChars()可以将jstring转换成UTF-8编码格式的c字符串,GetStringChars()可以将jstring转换成Unicode编码格式的c字符串。
  • 获取c字符串需要判断if(str == NULL),原因可能是JVM需要为这个字符串分配内存,会由于内存不足导致失败,抛出OutOfMemoryError异常。
  • 对于第二个参数isCopy,如果c字符串是指向JVM中jstring的同一份数据时为JNI_FALSE;如果c字符串是jstring的一份内存拷贝则为JNI_TRUE。若为JNI_FALSE我们不可能修改该c字符串,会破坏Java语言String不可变的原则。一般我们不需要关心是否复制的,那么可以传入NULL
  • 一旦Java对象指针被传递给c代码,那么GC就不会回收这个对象;所以我们需要调用ReleaseStringUTFChars()/ReleaseStringChars()这两个方法来释放资源:如果是获取了jstring的直接引用,则解除JVM的持有让GC可以回收;如果是内存拷贝则回收释放相应内存。
  • utf-8字符串以\0结尾,而Unicode不是;所以当ReleaseStringUTFChars()获取一个编码格式为Unicode的jstring时,返回的c字符串并不一定以\0结尾。建议直接以GetStringLength()GetStringUTFLength()来获取字符串长度;对于strlen()需要谨慎确保jstring指向的是一个utf-8的字符串。

构造新字符串

const char *str = "hello";
//将str转为utf-8编码的jstring字符串
jstring jstr = env->NewStringUTF(str);
const jchar *str1 = env->GetStringChars(jstr, NULL);
//将str1转为unicode编码的jstring字符串
jstring jstr1 = env->NewString(str1, env->GetStringLength(jstr));

知识点

  • 获取c字符串需要判断if(jstr == NULL),如果JVM内存不足则会抛出OutOfMemoryError异常,并返回NULL。
  • NewStringUTF()不需要传入字符串长度,因为utf-8默认以/0结尾;而NewString()则需要在第二个参数传入该字符串的长度。

其他字符串函数

//临界区字符串函数
const jchar *str = env->GetStringCritical(jstr, NULL);
//do something
env->ReleaseStringCritical(jstr, str);

//预先分配缓存字符串函数
jchar *str1 = static_cast<jchar *>(malloc(5 * sizeof(jchar)));
env->GetStringRegion(jstr, 0, 5, str1);
//do something
//自己释放str1 malloc的内存
free(str1);

知识点

  • Get/ReleaseStringCritical可以提高JVM返回直接指针的可能性,其会禁止GC的运行,但是其必须运行在"临界区"中,也就是在这两函数中间不能调用任何线程阻塞、或者本地JNI函数,否则容易引起死锁。
  • Get/ReleaseStringRegionGet/ReleaseStringUTFRegion对于小字符串来说是最佳选择,因为缓冲区可以提前分配;并且可以按需复制小段内容,因为它提供了一个开始索引和子字符串长度。

数组

基本类型数据数组

JNIEXPORT void JNICALL
    Java_Com_net168_xxx_Simple_testC(JNIEnv *env, jobject thiz, jintArray jarray)
{
    //获取整个数组内容
    jint *array1 = env->GetIntArrayElements(jarray, NULL);
    //do something
    env->ReleaseIntArrayElements(jarray, array1, 0);

    //获取数组长度
    jsize len = env->GetArrayLength(jarray);

    //预分配获取数组内容
    jint buf[10];
    env->GetIntArrayRegion(jarray, 0, 10, buf);
    //栈区域不用手动释放内存

    //在开始索引3的位置,开始更新5个数据
    env->SetIntArrayRegion(jarray, 3, 5, buf);

    //临界区获取数组内容
    jint *array2 = static_cast<jint *>(env->GetPrimitiveArrayCritical(jarray, NULL));
    //do something
    env->ReleasePrimitiveArrayCritical(jarray, array2, 0);
}

知识点

  • Get/Release<Type>ArrayElements函数可以获取到一个指向基本类型<Type>的指针,其可能指向jarray的同一份数据,而已进行内存的拷贝后返回;如果字符串处理一样,我们最后需要Release来释放资源。
  • GetArrayLength返回数组中的个数,这个在数组首次分配时确定下来。
  • Set/Get<Type>ArrayRegion可以在预先分配的c缓存区和jvm交换数据,函数还可以指定一个索引和长度对子数组进行操作。
  • Get/ReleasePrimitiveArrayCritical能提高返回直接指针的可能性,但是需要注意不能再临界区让线程阻塞或者使用其他jni函数,可能会导致死锁的发生。

对象数组

JNIEXPORT void JNICALL
    Java_Com_net168_xxx_Simple_testD(JNIEnv *env, jobject thiz, jobjectArray jarray)
{
    //获取jobjectArray的第一个jobject
    jobject obj1 = env->GetObjectArrayElement(jarray, 0);

    //将obj1设置到数组的第二个索引的位置
    env->SetObjectArrayElement(jarray, 1, obj1);
}

知识点

  • 对象数组不能一次性获取整个数组,需要用GetObjectArrayElement获取指定索引位置的jobect对象,还有用SetObjectArrayElement修改数组指定位置的元素。

结语

后续会陆续发布多篇JNI更加深入的文章。

本文同步发布于简书CSDN

End!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,393评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,790评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,391评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,703评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,613评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,003评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,507评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,158评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,300评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,256评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,274评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,984评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,569评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,662评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,899评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,268评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,840评论 2 339

推荐阅读更多精彩内容