Android Studio NDK 入门教程--Java与C++之间的简单数据转换与传递

Posted Wastrel_xyz

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Studio NDK 入门教程--Java与C++之间的简单数据转换与传递相关的知识,希望对你有一定的参考价值。

概述

本文将讲解Java与原生代码之间的数据转换,包括基础类型之间的转换,以及数组的传递与转换。

类型转换表

JAVA基础类型与C++之间的对应表

Java类型C/C++类型描述
booleanjboolean无符号8位整数
bytejbyte有符号8位整数
charjchar有符号16位整数
shortjshort有符号16位整数
intjint有符号32位整数
longjlong有符号64位整数
floatjfloat32位单精度浮点数
doublejdouble64位双精度浮点数

这一点可以从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++类型描述
Objecjobject任何Java对象
ClassjclassClass类对象
StringjstringString类对象
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对象的传递与修改

NDK开发 从入门到放弃(七:Android Studio 2.2 CMAKE 高效NDK开发)

Android Studio NDK 入门教程--JNI签名验证防止恶意调用