Android Studio NDK 入门教程--Java与C++之间的简单数据转换与传递
Posted Wastrel_xyz
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Studio NDK 入门教程--Java与C++之间的简单数据转换与传递相关的知识,希望对你有一定的参考价值。
概述
本文将讲解Java与原生代码之间的数据转换,包括基础类型之间的转换,以及数组的传递与转换。
类型转换表
JAVA基础类型与C++之间的对应表
Java类型 | C/C++类型 | 描述 |
---|---|---|
boolean | jboolean | 无符号8位整数 |
byte | jbyte | 有符号8位整数 |
char | jchar | 有符号16位整数 |
short | jshort | 有符号16位整数 |
int | jint | 有符号32位整数 |
long | jlong | 有符号64位整数 |
float | jfloat | 32位单精度浮点数 |
double | jdouble | 64位双精度浮点数 |
这一点可以从jni.h头文件中定义看到:
typedef unsigned char jboolean; /* unsigned 8 bits */
typedef signed char jbyte; /* signed 8 bits */
typedef unsigned short jchar; /* unsigned 16 bits */
typedef short jshort; /* signed 16 bits */
typedef int jint; /* signed 32 bits */
typedef long long jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
由上面的JNI定义代码可以看到,基础类型都是根据Java类型大小等价转换的。因此在使用Java传递过来的基础类型或者返回Java中的基础类型都可以直接使用。比如:
JNIEXPORT jint JNICALL
Java_com_example_wastrel_test_Test_BaseTypeTest(JNIEnv *env, jclass type,jint i)
return i+5;
Java引用类型与C++之间的对应表
Java类型 | C/C++类型 | 描述 |
---|---|---|
Objec | jobject | 任何Java对象 |
Class | jclass | Class类对象 |
String | jstring | String类对象 |
Object[] | jobjectArray | 对象数组 |
boolean[] | jbooleanArray | 布尔数组 |
byte[] | jbyteArray | 字节数组 |
char[] | jcharArray | 字符型数组 |
short[] | jshortArray | 短整型数组 |
int[] | jintArray | 整型数组 |
long[] | jlongArray | 长整型数组 |
float[] | jfloatArray | 浮点型数组 |
double[] | jdoubleArray | 双精度浮点型数组 |
/*
* Reference types, in C++
*/
class _jobject ;
class _jclass : public _jobject ;
class _jstring : public _jobject ;
class _jarray : public _jobject ;
class _jobjectArray : public _jarray ;
class _jbooleanArray : public _jarray ;
class _jbyteArray : public _jarray ;
class _jcharArray : public _jarray ;
class _jshortArray : public _jarray ;
class _jintArray : public _jarray ;
class _jlongArray : public _jarray ;
class _jfloatArray : public _jarray ;
class _jdoubleArray : public _jarray ;
class _jthrowable : public _jobject ;
typedef _jobject* jobject;
typedef _jclass* jclass;
typedef _jstring* jstring;
typedef _jarray* jarray;
typedef _jobjectArray* jobjectArray;
typedef _jbooleanArray* jbooleanArray;
typedef _jbyteArray* jbyteArray;
typedef _jcharArray* jcharArray;
typedef _jshortArray* jshortArray;
typedef _jintArray* jintArray;
typedef _jlongArray* jlongArray;
typedef _jfloatArray* jfloatArray;
typedef _jdoubleArray* jdoubleArray;
typedef _jthrowable* jthrowable;
typedef _jobject* jweak;
通过jni里的定义可以看到,任何引用类型(数组在Java中也是引用传递的)传递到C++里面只是一个指向Java对象的指针。因此引用类型不能直接使用,JNIEnv提供了大量的方法来完成了他们之间的转换。
使用JNIEnv完成数据之间的转换
这里只说明引用对象之间的转换,因为对于普通类型,Java与C++是互通的。
String的传递
这里将String单独拿出来讲解,因为String在Java中有着超高的使用频次,JNIEnv对String有相对应的转换函数。JNI里的函数定义如下:
/**转换成unicode相关函数 */
jstring NewString(const jchar*, jsize);
jsize GetStringLength(jstring);
const jchar* GetStringChars(jstring, jboolean* isCopy);
void ReleaseStringChars(jstring, const jchar*);
/** 转换成UTF-8相关函数 */
jstring NewStringUTF(const char*);
jsize GetStringUTFLength(jstring);
const char* GetStringUTFChars(jstring, jboolean* isCopy);
void ReleaseStringUTFChars(jstring, const char*);
当从 JNI 函数 GetStringChars中返回得到字符串B时,如果B是原始字符串java.lang.String 的拷贝,则isCopy被赋值为JNI_TRUE。如果B和原始字符串指向的是JVM中的同一份数据,则 isCopy被赋值为 JNI_FALSE。
当isCopy值为JNI_FALSE时,本地代码决不能修改字符串的内容,否则JVM中的原始字符串也会被修改,这会打破 JAVA语言中字符串不可变的规则。
通常,因为你不必关心JVM是否会返回原始字符串的拷贝,你只需要为 isCopy传递NULL作为参数。
由以上代码,我们可以清晰的看到String处理函数分为了Unicode和UTF-8两种编码类型,但在android中使用的UTF-8相关的函数,两种编码提供的功能都基本一致,从函数名就可以看出对应函数的功能。下面通过一个使用C++合并String的例子来演示如何使用:
//Java中定义Native函数
public native static String strConcat(String str1,String str2);
//生成C++函数并用C++实现字符串连接功能
#include "string.h"
JNIEXPORT jstring JNICALL Java_com_example_wastrel_test_Test_strConcat
(JNIEnv *env, jclass clazz, jstring str1, jstring str2)
//将jstring转换成const char*指针,使用const修饰符表示其内容不可被修改
const char* c1=env->GetStringUTFChars(str1, NULL);
const char* c2=env->GetStringUTFChars(str2, NULL);
//计算新字符串的长度
int size=strlen(c1)+strlen(c2);
//创建一个新的字符串,这里长度+1是为了使字符串有结尾标记'\\0'
char * n_char=new char[size+1];
//利用C标准库提供的字符串操作方法对字符串进行连接,这里需要include"string.h"头文件
strcpy(n_char,c1);
strcat(n_char,c2);
//将生成的新字符串转换成UTF的jstring
jstring rs=env->NewStringUTF(n_char);
//删除刚刚分配的内存 避免引起内存泄漏
delete [] n_char;
//通知JVM虚拟机Native代码不在持有字符串的引用,说明白点,就是告诉虚拟机我不使用它了,你可以回收了。
//因为在JVM中如果对象被引用,那么对象将不会被回收。
//这里为什么要传递jstring和生成的char*呢?是因为char*有可能是jstring的拷贝,如果是拷贝,那么char*就应该被删除。
env->ReleaseStringUTFChars(str1,c1);
env->ReleaseStringUTFChars(str2,c2);
return rs;
//然后我们在Java中调用该函数
print(Test.strConcat("里约奥运",",中国加油"));
注:本处以及往后代码中使用的print函数仅仅是把结果追加显示在界面上的TextView上代码如下:
private void print(String str)
tv.append(str+"\\n");
生成C函数过程请参照上一篇文章:http://blog.csdn.net/venusic/article/details/52121254/
运行结果:
基础数据的数组传递
数组传递跟字符串传递一样,Native收到的都是引用的形式,因此JNIEnv也提供了一些列的方法来完成数据的转换。因为不同数据类型之间的调用方式基本一致,此处使用int型数组作为讲解,int[]传递到Native后收到的是jintArray对象。
//获得数组的长度,该方法适用于所有jarray对象
jsize GetArrayLength(jarray array)
//在本地创建一个jint数组,这个数组只能通过SetIntArrayRegion赋值。
jintArray NewIntArray(jsize length);
//将jintArray转换成jint指针
jint* GetIntArrayElements(jintArray array, jboolean* isCopy);
//取出数组中的部分元素放在buf里
void GetIntArrayRegion(jintArray array, jsize start, jsize len, jint* buf);
//给jintArray按区间赋值
void SetIntArrayRegion(jintArray array, jsize start, jsize len,const jint* buf);
//释放jntArray,第一个参数表示传过来的jintArray,第二参数表示获取到的本地数组指针
//第三个参数需要重点说明,该参数有三个取值:0、JNI_COMMIT、JNI_ABORT
//取值 零(0) 时,更新数组并释放所有元素;
//取值 JNI_COMMIT 时,更新但不释放所有元素;
//取值 JNI_ABORT 时,不作更新但释放所有元素;
//一般实际应用中取0较多
void ReleaseIntArrayElements(jintArray array, jint* elems,jint mode);
例子1:来自JNI的int数组
//Java中定义Native函数,size表示返回数组的大小
public native static int[] getIntArray(int size);
//生成Native函数并实现方法
#include "stdlib.h"
#include "time.h"
//定义随机数产生宏 表示产生0~x之间的随机数
#define random(x) (rand()%x)
JNIEXPORT jintArray JNICALL Java_com_example_wastrel_test_Test_getIntArray
(JNIEnv *env, jclass clazz, jint size)
//用时间变量初始化随机数产生器
srand((int)time(0));
jint* rs=new jint[size];
for (int i=0;i<size;i++)
//调用宏产生0~100的随机数
rs[i]=random(100);
//通过JNIEnv的NewIntArray方法new一个jintArray对象
jintArray array=env->NewIntArray(size);
//把产生的随机数值赋值给jintArray
env->SetIntArrayRegion(array,0,size,rs);
return array;
//Java中调用函数
int []rs=Test.getIntArray(10);
print("来自于JNI的Int数组");
print(IntArrayToString(rs));
/**将int[]转换成逗号分隔便于显示的辅助函数*/
private String IntArrayToString(int[] ints)
StringBuilder str=new StringBuilder();
str.append('[');
for (int i:ints)
str.append(i);
str.append(',');
str.deleteCharAt(str.length()-1);
str.append(']');
return str.toString();
运行结果:
例子2:使用JNI对例1返回的数组进行排序
//声明Java Native函数,参数为int[]
public native static void sortIntArray(int []ints);
//实现C++函数
JNIEXPORT void JNICALL Java_com_example_wastrel_test_Test_sortIntArray
(JNIEnv *env, jclass clazz, jintArray array)
//获得传递过来的数组长度
jsize size=env->GetArrayLength(array);
//将数组转换成Java指针
jint* jints=env->GetIntArrayElements(array,NULL);
//简单的冒泡排序
for (int i = 0; i <size-1 ; ++i)
for (int j = 0; j <size-1-i ; ++j)
if(jints[j]<jints[j+1])
int t=jints[j];
jints[j]=jints[j+1];
jints[j+1]=t;
//将排序结果更新到Java数组中,第三个参数等于0表明更新到原数组并释放所有元素
env->ReleaseIntArrayElements(array,jints,0);
return;
//在Java中调用
print("通过JNI对int数组排序:");
Test.sortIntArray(rs);
print(IntArrayToString(rs));
这里可以看到,我们并没有返回数组,而是通过ReleaseIntArrayElements函数将结果更新到Java数组中。随之我们在Java中的数组值已经变更。
运行结果:
其他基础类型数组的传递
其他基础类型中的数组传递与int[]几乎一致,函数名更换成各自的类型即可。这里不在过多叙述,我想有了上面的例子,很容易明白别的基础数据类型传递使用。
示例程序下载地址:http://download.csdn.net/detail/venusic/9604128
以上是关于Android Studio NDK 入门教程--Java与C++之间的简单数据转换与传递的主要内容,如果未能解决你的问题,请参考以下文章
Android Studio NDK 入门教程--优雅的在C++中输出Logcat
Android Studio NDK 入门教程--JNI动态注册本地方法
Android Studio NDK 入门教程--Java与C++之间的类型签名
Android Studio NDK 入门教程--Java对象的传递与修改