在 Android Jni 编程中如何接收和返回 Java 不同类型变量或对象
(2012-10-18 10:51:29)
标签:
杂谈 |
分类: 开源项目之Android |
(注:由于新浪博客不支持
C 注释,所以请将 /^ ^/ 想像替换为 /星 星/)
JNINativeMethod::signature 描述字符串字符意义说明:
1)基本类型对应关系:
标识符 Jni
类型
C 类型
V
void
void
Z
jboolean
boolean
I
jint
int
J
jlong
long
D
jdouble
double
F
jfloat
float
B
jbyte
byte
C
jchar
char
S
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
JNINativeMethod::signature 描述字符串字符意义说明:
1)基本类型对应关系:
标识符
2)基本类型数组:(则以 [ 开始,用两个字符表示)
标识串
3)类(class):(则以 L 开头,以 ; 结尾,中间是用 / 隔开的 包 及 类名)
标识串 Java 类型 Jni 类型
L包1/包n/类名;
例子:
Ljava/net/Socket; Socket
4)例外(String 类):
标识串
Ljava/lang/String;
5)嵌套类(类位于另一个类之中,则用$作为类名间的分隔符)
标识串
L包1/包n/类名$嵌套类名;
例子:
Landroid/os/FileUtils$FileStatus;
首先可以肯定地是 本地动态库函数 不但可以传递 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
1 ^/
&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
1 ^/
&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" );
String、数组 和 自定义的类。
1) Java 基本类型的传递
/^
#ifdef HAVE_INTTYPES_H
#else
#endif
/^
typedef void*
typedef jobject
typedef jobject
typedef jobject
typedef jarray
typedef jarray
typedef jarray
typedef jarray
typedef jarray
typedef jarray
typedef jarray
typedef jarray
typedef jarray
typedef jobject
typedef jobject
2) Java String 类型的传递
在 jni.h 中找到如下 C 语言类型定义
/^
typedef void*
typedef jobject
注:Java String 和 C++ string 也是不对等的,在 jni.h 中找到 C++ 语言类型定义如下:
/^
class _jobject {};
class _jstring : public _jobject {};
typedef _jobject*
typedef _jstring*
下面我们看一个以 Java String 做为接收参数和返回值类型的例子:
/^
JNIEXPORT jstring JNICALL
Java_wzh_nsc_nativecode_GetWelcomeWords( JNIEnv * env,
{
}
3) Java 数组类型的传递
Jni 为 Java 基本类型数组提供了如下种类型:
/^
typedef void*
typedef jobject
typedef jarray
typedef jarray
typedef jarray
typedef jarray
typedef jarray
typedef jarray
typedef jarray
typedef jarray
typedef jarray
1. 下面我们先来看一个以 Java 一维数组类型 做为接收参数和返回值类型的例子:
/^
JNIEXPORT jintArray JNICALL
Java_wzh_nsc_nativecode_DealIntegerArray( JNIEnv
{
}
2. 下面我们先来看一个以 Java 二维数组类型 做为接收参数和返回值类型的例子:
/^
JNIEXPORT jobjectArray JNICALL
Java_wzh_nsc_nativecode_DealTwoDimensionalIntege
{
}
4)在 native 方法中创建 Java 用户自定义类对象
如果要创建 Java 用户自定义类对象,首先要能访问类的构造函数,
/^ 首先要创建一个 Java 用户自定义类的引用,通过 FindClass 函数来完成,
jclass jcCCustomClass = (*env)->FindClass( env, "Lwzhnsc/CCustomClass;" );
/^ 然后通过 GetMethodID 函数获得这个类的构造函数,原型如下:
jmethodID jmiInit = (*env)->GetMethodID( env,
//
注: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 中就可以接收到本机方法中抛出的异常。
jvalue jvArgs[1];
jvArgs[0].d = 1.369;
/^ 生成了一个 Java 用户自定义类对象 ^/
jobject joCustomClassObj = (*env)->NewObjectA( env,
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 中就可以接收到本机方法中抛出的异常。