加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

在 Android Jni 编程中如何接收和返回 Java 不同类型变量或对象

(2012-10-18 10:51:29)
标签:

杂谈

分类: 开源项目之Android
(注:由于新浪博客不支持 C 注释,所以请将 /^ ^/ 想像替换为 /星 星/)

JNINativeMethod::signature 描述字符串字符意义说明:
1)基本类型对应关系:
标识符  Jni 类型       C 类型
    void           void
    jboolean       boolean
    jint           int
    jlong          long
    jdouble        double
    jfloat         float
    jbyte          byte
    jchar          char
    jshort         short

2)基本类型数组:(则以 [ 开始,用两个字符表示)
标识串  Jni 类型        C 类型
  [Z   jbooleanArray  boolean[]
  [I   jintArray      int[]
  [J   jlongArray     long[]
  [D   jdoubleArray   double[]
  [F   jfloatArray    float[]
  [B   jbyteArray     byte[]
  [C   jcharArray     char[]
  [S   jshortArray    short[]

3)类(class):(则以 L 开头,以 ; 结尾,中间是用 / 隔开的 包 及 类名)
标识串        Java 类型  Jni 类型
L包1/包n/类名;     类名     jobject
例子:
Ljava/net/Socket; Socket      jobject

4)例外(String 类):
标识串               Java 类型  Jni 类型
Ljava/lang/String;  String    jstring

5)嵌套类(类位于另一个类之中,则用$作为类名间的分隔符)
标识串                         Java 类型  Jni 类型
L包1/包n/类名$嵌套类名;              类名      jobject
例子:
Landroid/os/FileUtils$FileStatus;  FileStatus  jobject

首先可以肯定地是 本地动态库函数 不但可以传递 Java 的基本类型,也可以传递更复杂的类型,如:
String、数组 和 自定义的类。


1) Java 基本类型的传递

   Java 中的基本类型包括 boolean、byte、char、short、int、long、float、double 这 8 种,
   分别对应 jni 类型是 jboolean、jbyte、jchar、jshort、jint、jlong、jfloat、jdouble,
   在 jni.h 中找到如下 C 语言类型定义,所以可以直接使用:
/^

 * Primitive types that match up with Java equivalents.
 ^/
#ifdef HAVE_INTTYPES_H

    /^ C99 ^/
    # include
    typedef uint8_t         jboolean;       /^ unsigned 8 bits ^/
    typedef int8_t          jbyte;          /^ signed 8 bits ^/
    typedef uint16_t        jchar;          /^ unsigned 16 bits ^/
    typedef int16_t         jshort;         /^ signed 16 bits ^/
    typedef int32_t         jint;           /^ signed 32 bits ^/
    typedef int64_t         jlong;          /^ signed 64 bits ^/
    typedef float           jfloat;         /^ 32-bit IEEE 754 ^/
    typedef double          jdouble;        /^ 64-bit IEEE 754 ^/
#else

    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 ^/
#endif


/^

 * Reference types, in C.
 ^/
typedef void*           jobject;

typedef jobject         jclass;

typedef jobject         jstring;

typedef jobject         jarray;

typedef jarray          jobjectArray;

typedef jarray          jbooleanArray;

typedef jarray          jbyteArray;

typedef jarray          jcharArray;

typedef jarray          jshortArray;

typedef jarray          jintArray;

typedef jarray          jlongArray;

typedef jarray          jfloatArray;

typedef jarray          jdoubleArray;

typedef jobject         jthrowable;

typedef jobject         jweak;


2) Java String 类型的传递

在 jni.h 中找到如下 C 语言类型定义

/^

 * Reference types, in C.
 ^/
typedef void*           jobject;

typedef jobject         jstring;

注:Java String 和 C++ string 也是不对等的,在 jni.h 中找到 C++ 语言类型定义如下:

/^

 * Reference types, in C++
 ^/
class _jobject {};

class _jstring : public _jobject {};

typedef _jobject*       jobject;

typedef _jstring*       jstring;


下面我们看一个以 Java String 做为接收参数和返回值类型的例子:

/^

 * Class:     wzh_nsc_nativecode
 * Method:    GetWelcomeWords
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 ^/
JNIEXPORT jstring JNICALL

Java_wzh_nsc_nativecode_GetWelcomeWords( JNIEnv * env,

                                         jobject  this,
                                         jstring  jstrUserName )
{

    char* pcTransformStr = NULL;
    char  caCombinStr[200];
    jboolean jbIsCopy = JNI_FALSE;
    /^ 因为 C 语言是如下定义 JNIEnv 类型的 ^/
    /^ typedef const struct JNINativeInterface* JNIEnv; ^/
    /^ 要用(*env)先将二级指针转成一级指针后方可调用JNINativeInterface结构成员变量 ^/
    /^ 如下为 GetStringUTFChars 函数的原型 ^/
    /^ const char* (*GetStringUTFChars)(JNIEnv*, jstring, jboolean*); ^/
    /^ 如果 用户名字符串指针不为空 的话,则 ^/
    if ( NULL != jstrUserName )
    {
        /^ 开辟新的内存,
           然后把 Java 的 String jstrUserName 拷贝到这个内存中,
           最后返回指向这个内存地址的指针 ^/
        pcTransformStr = (*env)->GetStringUTFChars( env,
                                                    jstrUserName,
        /^ 第三个参数用来标示是否对 Java 的 String jstrUserName 进行了拷贝的。
           如果赋不是 NULL,
           而是 jboolean 指针的话则会给该指针所指向的 jboolean 变量内存中传入
           JNI_TRUE 或 JNI _FALSE标示是否进行了拷贝。^/
        /^
         * Manifest constants.
        #define JNI_FALSE   0
        #define JNI_TRUE    ^/
                                                    &jbIsCopy );
    }

    /^ 如果 取得用户名失败 的话,则 ^/
    if( NULL == pcTransformStr )
    {
        /^ 输出的 log 一般是到 /dev/log/ 下的三个设备中,可以用 logcat 工具查看 ^/
        __android_log_write( ANDROID_LOG_INFO,            /^ 日志信息 ^/
                             "nativecode",                /^ 日志标签 ^/
                             /^ 日志内容 ^/
                             "Import username char string is empty" );

        /^ 返回用户名不可识别 ^/
        return NULL;
    }
 
    memset( caCombinStr, 0, sizeof(caCombinStr) );
    sprintf( caCombinStr,
             "%s Welcome to you! IsCopy = %d",
             pcTransformStr,
             jbIsCopy );

    /^ 输出的 log 一般是到 /dev/log/ 下的三个设备中,可以用 logcat 工具查看 ^/
    __android_log_write( ANDROID_LOG_INFO, /^ 日志信息 ^/
                         "nativecode",     /^ 日志标签 ^/
                         caCombinStr );    /^ 日志内容 ^/

    /^ 在你使用完转换生成的字符串缓冲区之后,需要显示调用 ReleaseStringUTFChars 函数,
       让 Java 虚拟机释放转换生成的字符串缓冲区空间,
       如果不显示的调用的话,Java 虚拟机中会一直保存该缓冲区空间,而不会被垃圾回收器回收,
       因此就会导致内存溢出。^/
    /^ 如下为 ReleaseStringUTFChars 函数的原型 ^/
    /^ void (*ReleaseStringUTFChars)(JNIEnv*, jstring, const char*); ^/
    (*env)->ReleaseStringUTFChars( env,
                                   jstrUserName,
                                   pcTransformStr );

    /^ 如下为 NewStringUTF 函数的原型 ^/
    /^ jstring (*NewStringUTF)(JNIEnv*, const char*); ^/
    return (*env)->NewStringUTF( env,
                                 caCombinStr );
}


3) Java 数组类型的传递

Jni 为 Java 基本类型数组提供了如下种类型:

/^

 * Reference types, in C.
 ^/
typedef void*           jobject;

typedef jobject         jarray;

typedef jarray          jobjectArray;

typedef jarray          jbooleanArray;

typedef jarray          jbyteArray;

typedef jarray          jcharArray;

typedef jarray          jshortArray;

typedef jarray          jintArray;

typedef jarray          jlongArray;

typedef jarray          jfloatArray;

typedef jarray          jdoubleArray;


1. 下面我们先来看一个以 Java 一维数组类型 做为接收参数和返回值类型的例子:

/^

 * Class:     wzh_nsc_nativecode
 * Method:    DealIntegerArray
 * Signature: ([I)[I
 * Reference types, in C.
 * typedef void*           jobject;
 * typedef jobject         jarray;
 * typedef jarray          jintArray;
 ^/

JNIEXPORT jintArray JNICALL

Java_wzh_nsc_nativecode_DealIntegerArray( JNIEnv  * env,

                                          jobject   this,
                                          jintArray jiaTestScore )
{

    jboolean jbIsCopy = JNI_FALSE;
    /^ 获取数组的长度 ^/
    /^ 如下为 GetArrayLength 函数的原型 ^/
    /^ jsize (*GetArrayLength)(JNIEnv*, jarray); ^/
    /^ "cardinal indices and sizes"
       typedef jint jsize; ^/
    jsize jiArraySize = (*env)->GetArrayLength( env,
                                                jiaTestScore );
 
    /^ 获取一个指向数组元素的指针 ^/
    /^ 如下为 GetIntArrayElements 函数的原型 ^/
    /^ jint* (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*); ^/
    jint *piTestResult = (*env)->GetIntArrayElements( env,
                                                      jiaTestScore,
                /^ 第三个参数用来表明返回的数组指针是指向数据的真正存区(值为1时),
                   还是指向拷贝了数组元素值的另一存区(值为0)^/
                /^
                 * Manifest constants.
                   #define JNI_FALSE   0
                   #define JNI_TRUE    ^/
                                                      &jbIsCopy );

    /^ 如果 数组元素个数不大于零(0) 或者 获得首元素指针失败 的话,则 ^/
    if( 0 >= jiArraySize ||
        NULL == piTestResult )
    {
        /^ 输出的 log 一般是到 /dev/log/ 下的三个设备中,可以用 logcat 工具查看 ^/
        __android_log_write( ANDROID_LOG_INFO,                /^ 日志信息 ^/
                             "nativecode",                    /^ 日志标签 ^/
                             "jintArray parameter is NULL" ); /^ 日志内容 ^/
        /^ 返回无法处理数组 ^/
        return 0;
    }
 
    /^ 新建指定元素个数的整型数组函数 NewIntArray 原型如下: ^/
    /^ jintArray (*NewIntArray)(JNIEnv*, jsize); ^/
    jintArray jiaModifyScore = (*env)->NewIntArray( env,
                                                    jiArraySize );
    jint *piModifyScore = (*env)->GetIntArrayElements( env,
                                                       jiaModifyScore,
                                                       &jbIsCopy );
    /^ 如果 新建指定元素个数的整型数组失败 或者 获得首元素指针失败 的话,则 ^/
    if( 0 >= jiaModifyScore ||
        NULL == piModifyScore )
    {
        /^ 输出的 log 一般是到 /dev/log/ 下的三个设备中,可以用 logcat 工具查看 ^/
        __android_log_write( ANDROID_LOG_INFO,            /^ 日志信息 ^/
                             "nativecode",                /^ 日志标签 ^/
                             "New jintArray is failed" ); /^ 日志内容 ^/
        /^ 返回无法处理数组 ^/
        return 0;
    }

    unsigned int uiForCounter = 0;
    for( uiForCounter = 0;
         uiForCounter < jiArraySize;
         uiForCounter++ )
    {
        piModifyScore[uiForCounter] = piTestResult[uiForCounter] + 10;
    }

    /^ 如下为 ReleaseIntArrayElements 函数的原型 ^/
    /^ void (*ReleaseIntArrayElements)(JNIEnv*, jintArray, jint*, jint); ^/
    /^ 该函数与 GetIntArrayElements 函数可以说是对应的。
       它完成的功能是释放资源和数据更新。
       由于 Java 的垃圾收集具有可能改变内存中对象的位置,如不采取必要措施,
       被访问的数组指针就可能不再指向正确的存区。因此,对于数组,要么把它“钉”在固定的存区,
       要么把它拷贝至固定的存区,总之在访问它的期间要使数组元素总在原地。
       作完操作之后,再调用这个函数,解除对它的固定。
       另外,在调用这个函数之前,所有更新都没有作用在数组本身上。
       第三个参数就是决定更新与否的。
       取值 零(0) 时,更新数组并释放所有元素;
       取值 JNI_COMMIT 时,更新但不释放所有元素;
       取值 JNI_ABORT 时,不作更新但释放所有元素;
       #define JNI_COMMIT 1 // copy content, do not free buffer
       #define JNI_ABORT  2 // free buffer w/o copying back ^/
    (*env)->ReleaseIntArrayElements( env,
                                     jiaTestScore,
                                     piTestResult,
                                     0 );
    (*env)->ReleaseIntArrayElements( env,
                                     jiaModifyScore,
                                     piModifyScore,
                                     0 );
    return jiaModifyScore;
}


2. 下面我们先来看一个以 Java 二维数组类型 做为接收参数和返回值类型的例子:

/^

 * Class:     wzh_nsc_nativecode
 * Method:    DealTwoDimensionalIntegerArray
 * Signature: ([[I)[[I
 ^/
JNIEXPORT jobjectArray JNICALL

Java_wzh_nsc_nativecode_DealTwoDimensionalIntegerArray( JNIEnv *env,

                                                        jobject this,
                                                        jobjectArray joaTestInt2DArray )
{

    char caCombinStr[200];
    jboolean jbIsCopy = JNI_FALSE;

    /^ 注:二维数组其实就是一维数组的指针数组,每个元素都指向一个一维数组 ^/
    /^ 获取数组的长度 ^/
    /^ 如下为 GetArrayLength 函数的原型 ^/
    /^ jsize (*GetArrayLength)(JNIEnv*, jarray); ^/
    /^ "cardinal indices and sizes"
       typedef jint jsize; ^/
    int size = (*env)->GetArrayLength( env, joaTestInt2DArray );
    /^ 得到下标为[0][]的一维数组 ^/
    /^ 如下为 GetObjectArrayElement 函数的原型 ^/
    /^ jobject (*GetObjectArrayElement)(JNIEnv*, jobjectArray, jsize); ^/
    jarray jaIntArray = (*env)->GetObjectArrayElement( env,
                                                       joaTestInt2DArray,
                                                       0 );
    /^ 得到下标为[0][]的一维数组元素个数 ^/
    int length = (*env)->GetArrayLength( env, jaIntArray );
   
    memset( caCombinStr, 0, sizeof(caCombinStr) );
    sprintf( caCombinStr,
             "size = %d, length = %d",
             size,
             length );

    /^ 输出的 log 一般是到 /dev/log/ 下的三个设备中,可以用 logcat 工具查看 ^/
    __android_log_write( ANDROID_LOG_INFO,            /^ 日志信息 ^/
                         "nativecode",                /^ 日志标签 ^/
                         caCombinStr ); /^ 日志内容 ^/

    /^ 在JNI中,二维数组和String数组都被视为object数组,因为 数组 和String被视为object ^/
    jobjectArray joaReturnInt2DArray;

    /^ 如下是创建一个jclass的引用,
       因为 joaReturnInt2DArray 的元素是 一维 int 数组 的引用,
       所以 jcIntArray 必须是 一维 int 数组 的引用,
       这一点是如何保证的呢?
       注意:FindClass 的参数 "[I",原型如下:
       jclass (*FindClass)(JNIEnv*, const char*);
       JNI就是通过它来确定引用的类型的,I表示是int类型,[标识是数组。 ^/
       jclass jcIntArray = (*env)->FindClass( env, "[I" );

    /^ 为 joaReturnInt2DArray 分配空间 ^/
    /^ 如下为 NewObjectArray 函数的原型 ^/
    /^ jobjectArray (*NewObjectArray)(JNIEnv*, jsize, jclass, jobject); ^/
    joaReturnInt2DArray = (*env)->NewObjectArray( env,
                                                  size,
                                                  jcIntArray,
                                                  NULL );
    /^ 根据参数传入的二维数组大小创建返回的二维数组 ^/
    int i = 0;
    for( i = 0;
         i < size;
         i++ )
    {
        /^ 得到下标为[0][]的一维数组 ^/
        /^ 如下为 GetObjectArrayElement 函数的原型 ^/
        /^ jobject (*GetObjectArrayElement)(JNIEnv*, jobjectArray, jsize); ^/
        jaIntArray = (*env)->GetObjectArrayElement( env, joaTestInt2DArray, i );
       
        /^ 新建指定元素个数的整型数组函数 NewIntArray 原型如下: ^/
        /^ jintArray (*NewIntArray)(JNIEnv*, jsize); ^/
        jintArray jiaTmpIntArray = (*env)->NewIntArray( env, length );
       
        int j = 0;
        for( j = 0;
             j < length;
             j++ )
        {
            jint *piSrc = (*env)->GetIntArrayElements( env,
                                                       jaIntArray, /^ 传入的数组 ^/
                                                       &jbIsCopy );

            jint *piDes = (*env)->GetIntArrayElements( env,
                                                       jiaTmpIntArray, /^ 传入的数组 ^/
                                                       &jbIsCopy );
       
            piDes[j] = piSrc[j] + 1;

            (*env)->ReleaseIntArrayElements( env,
                                             jaIntArray,
                                             piSrc,
                                             0 );
            (*env)->ReleaseIntArrayElements( env,
                                             jiaTmpIntArray,
                                             piDes,
                                             0 );

            /^ 如果参数传入的是字符串数组的话,可以如下获取每一个字符串
            jstring string = ((*env)->GetObjectArrayElement(env, myarray, i));
            const char * chars = (*env)->GetStringUTFChars(env, string, 0);
            printf("%s n", chars);
            (*env)->ReleaseStringUTFChars(env, string, chars); ^/
        }

        /^ 指定临时一维数组的首地址为数组指针数组(二维数组)的第i个元素的值,原型如下: ^/
        /^ void (*SetObjectArrayElement)(JNIEnv*, jobjectArray, jsize, jobject); ^/
        (*env)->SetObjectArrayElement( env, joaReturnInt2DArray, i, jiaTmpIntArray );

        /^ 删除对数组的当前引用,原型如下: ^/
        /^ void (*DeleteLocalRef)(JNIEnv*, jobject); ^/   
        (*env)->DeleteLocalRef( env, jiaTmpIntArray );
    }
   
    return joaReturnInt2DArray;
}


4)在 native 方法中创建 Java 用户自定义类对象

如果要创建 Java 用户自定义类对象,首先要能访问类的构造函数,

/^ 首先要创建一个 Java 用户自定义类的引用,通过 FindClass 函数来完成,

   参数同前面介绍的创建 java/lang/String 对象的引用类似,只不过类名称变成自定义类的名称 ^/
jclass jcCCustomClass = (*env)->FindClass( env, "Lwzhnsc/CCustomClass;" );

/^ 然后通过 GetMethodID 函数获得这个类的构造函数,原型如下:

   jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
   注意这里方法的名称是”<init>”,它表示这是一个构造函数 ^/
jmethodID jmiInit = (*env)->GetMethodID( env,

                                         jcCCustomClass,
                                         "<init>",
                                         "(D)V" );
// 注:JNI规定调用构造函数的时候传递的方法名应该为<init>
jvalue jvArgs[1];

jvArgs[0].d = 1.369;

/^ 生成了一个 Java 用户自定义类对象 ^/

jobject joCustomClassObj = (*env)->NewObjectA( env,

                                               jcCCustomClass,
                                               jmiInit,
                                               jvArgs );

5)将异常抛给 Java 代码处理

JNI提供了实现这种功能的机制,可以通过如下面代码抛出一个 Java 代码可以接收的异常。

jclass jcError;


/^ void (*ExceptionDescribe)(JNIEnv*); ^/

(*env)->ExceptionDescribe( env );

/^ void (*ExceptionClear)(JNIEnv*); ^/

(*env)->ExceptionClear( env );


jcError = (*env)->FindClass( env, “Ljava/lang/IllegalArgumentException” );


/^ jint (*ThrowNew)(JNIEnv *, jclass, const char *); ^/

(*env)->ThrowNew( env, jcError, “throw from C code” );


如果要抛出其他类型的异常,替换掉FindClass的参数即可。

这样,在 Java 中就可以接收到本机方法中抛出的异常。

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有