JNI中参数的传递与操作

来源:互联网 发布:java中get(class) 编辑:程序博客网 时间:2024/05/18 03:26
JNI的所有的本地方法的第一个参数都是指向JNIEnv结构的。这个结构是用来调用JNI函数的。第二个参数jclass/jobject的意义,要看方法是不是静态的(static)或者实例(Instance)的。前者,jclass代表一个类对象的引用,而后者是被调用的方法所属对象的引用。 从第三个参数开始的才是java函数本身传递的参数。
返回值和参数类型根据等价约定映射到本地C/C++类型,如表A所示。有些类型,在本地代码中可直接使用,而有些类型只有通过JNI调用操作。
表A:

Java类型

Java本地C/C++类型

描述

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中的基本类型包括boolean,byte,char,short,int,long,float,double这样几种,
如果你用这几种类型做native方法的参数,当你通过javah -jni生成.h文件的时候,只要看一下生成的.h文件,就会一清二楚,
这些java类型分别对应的java本地C/C++类型是 jboolean,jbyte,jchar,jshort,jint,jlong,jfloat,jdouble 。这几种类型几乎都可以当成对应的C/C++类型来用。
如果想要返回一个java的基本类型,可以像操作C/C++类型一样,创建一个对应的java本地C/C++类型,操作完成后,最后直接返回它就可以了。
二、String
Java的String和C++的string是不能对等起来的,所以处理起来比较麻烦。
先看一个例子,
class Prompt {
// native method that prints a prompt and reads a line
private native String getLine(String prompt);
public static void main(String args[]) {
Prompt p = new Prompt();
String input =p.getLine("Type a line: ");
System.out.println("User typed: " + input);
}
static {
System.loadLibrary("Prompt");
}
}
在这个例子中,我们要实现一个native方法
String getLine(String prompt);
读入一个String参数,返回一个String值。
通过执行javah -jni得到的头文件是这样的
#include <jni.h>
#ifndef _Included_Prompt
#define _Included_Prompt
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jstring JNICALL Java_Prompt_getLine(JNIEnv *env, jobject this, jstring prompt);
#ifdef __cplusplus
}
#endif
#endif
jstring是JNI中对应于String的类型,但是和基本类型不同的是,jstring不能直接当作C++的string用。如果你用
cout << prompt << endl;
编译器肯定会扔给你一个错误信息的。
其实要处理jstring有很多种方式,这里只讲一种我认为最简单的方式,看下面这个例子,
#include "Prompt.h"
#include <iostream>
JNIEXPORTjstring JNICALL Java_Prompt_getLine(JNIEnv *env, jobject obj, jstring prompt)
{
const char* str;
str = env->GetStringUTFChars(prompt, false);
if(str == NULL) {
return NULL; /* OutOfMemoryError already thrown */
}
std::cout << str << std::endl;
env->ReleaseStringUTFChars(prompt, str);
char* tmpstr = "return string succeeded";
jstring rtstr = env->NewStringUTF(tmpstr);
return rtstr;
}
String不能直接被C++程序使用,需要先用env->GetStringUTFChars把它转化为UTF编码形式的char*再进行处理。
如:str = env->GetStringUTFChars(prompt, false);
如果我们想对String进行修改的话,需要调用env->ReleaseStringUTFChars把char*反射到String来进行修改。
如:env->ReleaseStringUTFChars(prompt, str);
如果想返回一个java的String类型的话,我们可以通过env->NewStringUTF命令用一个char*来创建一个jstring,然后让该jstring返回就可以。
如:jstring rtstr = env->NewStringUTF(tmpstr);
上面的GetStringUTFChars,ReleaseStringUTFChars,NewStringUTF都是JNI提供的处理String类型的函数,更多的JNI函数请查看jni.h。

三、数组
JNI提供了对Java数组进行操作的功能。
它提供了两类函数:一类用于操作java的简单型数组,另一类用于是操作对象类型数组的。
简单数组
因为速度的原因,先通过GetXXXArrayElements函数把简单类型的数组转化成本地类型的数组,并返回其数组的指针,然后通过该指针来对拷贝数组进行处理。
对拷贝数组处理完后,通过ReleaseXXXArrayElements函数把修改后的拷贝数组的反射到java数组,
然后释放所有相关的资源。
注意:GetXXXArrayElements函数(见表B)中,XXX代表了数组的类型。这种函数把Java数组看成参数,返回一个指向对应的本地类型的数组的指针
表B

函数

Java数组本地C/C++类型

Java本地C/C++类型

GetBooleanArrayElements

jbooleanArray

Jboolean

GetByteArrayElements

jbyteArray

Jbyte

GetCharArrayElements

jcharArray

Jchar

GetShortArrayElements

jshortArray

Jshort

GetIntArrayElements

jintArray

Jint

GetLongArrayElements

jlongArray

Jlong

GetFloatArrayElements

jfloatArray

Jfloat

GetDoubleArrayElements

jdoubleArray

Jdouble

GetXXXArrayElements系列函数如下
jboolean * GetBooleanArrayElements (jbooleanArray val0, jboolean * val1)
jbyte * GetByteArrayElements (jbyteArray val0, jboolean * val1)
jchar * GetCharArrayElements (jcharArray val0, jboolean * val1)
jshort * GetShortArrayElements (jshortArray val0, jboolean * val1)
jint * GetIntArrayElements (jintArray val0, jboolean * val1)
jlong * GetLongArrayElements (jlongArray val0, jboolean * val1)
jfloat * GetFloatArrayElements (jfloatArray val0, jboolean * val1)
jdouble * GetDoubleArrayElements (jdoubleArray val0, jboolean * val1)
第一个参数,val0是你要操作的数组。
第二参数,val1什么意思呢?不知道。一般用0就可以。
ReleaseXXXAarryElements系列函数如下
void ReleaseBooleanArrayElements (jbooleanArray val0, jboolean * val1, jint val2)
void ReleaseByteArrayElements (jbyteArray val0, jbyte * val1, jint val2)
void ReleaseCharArrayElements (jcharArray val0, jchar * val1, jint val2)
void ReleaseShortArrayElements (jshortArray val0, jshort * val1, jint val2)
void ReleaseIntArrayElements (jintArray val0, jint * val1, jint val2)
void ReleaseLongArrayElements (jlongArray val0, jlong * val1, jint val2)
void ReleaseFloatArrayElements (jfloatArray val0, jfloat * val1, jint val2)
void ReleaseDoubleArrayElements (jdoubleArray val0, jdouble * val1, jint val2)
参数意义
把数组val0从第0个开始的val2个元素设置为val1地址开始的val2个元素的值
如果想返回一个新的数组,可以先用NewXXXArray函数创建一个Java数组本地C/C++类型数组,然后通过ReleaseXXXAarryElements/SetXXXArrayRegion函数设置数组值,最后返回直接返回该数组就可以了。
另外我们还可以用GetXXXArrayRegion函数取得数组某段的数据。这来的XXX表示是什么类型的简单数组
NewXXXArray系列函数如下:
jbooleanArray NewBooleanArray (jsize val0)
jbyteArray NewByteArray (jsize val0)
jcharArray NewCharArray (jsize val0)
jshortArray NewShortArray (jsize val0)
jintArray NewIntArray (jsize val0)
jlongArray NewLongArray (jsize val0)
jfloatArray NewFloatArray (jsize val0)
jdoubleArray NewDoubleArray (jsize val0)
第一参数,jsize val0表示要创建的数组的大小.
SetXXXArrayRegion系列函数如下:
void SetBooleanArrayRegion (jbooleanArray val0, jsize val1, jsize val2, jboolean * val3)
void SetByteArrayRegion (jbyteArray val0, jsize val1, jsize val2, jbyte * val3)
void SetCharArrayRegion (jcharArray val0, jsize val1, jsize val2, jchar * val3)
void SetShortArrayRegion (jshortArray val0, jsize val1, jsize val2, jshort * val3)
void SetIntArrayRegion (jintArray val0, jsize val1, jsize val2, jint * val3)
void SetLongArrayRegion (jlongArray val0, jsize val1, jsize val2, jlong * val3)
void SetFloatArrayRegion (jfloatArray val0, jsize val1, jsize val2, jfloat * val3)
void SetDoubleArrayRegion (jdoubleArray val0, jsize val1, jsize val2, jdouble * val3)
参数的意义
表示把数组val0从val1开始的val2个元素设置为val3内存地址开始的val2个元素
GetXXXArrayRegion系列函数如下
void GetBooleanArrayRegion (jbooleanArray val0, jsize val1, jsize val2, jboolean * val3)
void GetByteArrayRegion (jbyteArray val0, jsize val1, jsize val2, jbyte * val3)
void GetCharArrayRegion (jcharArray val0, jsize val1, jsize val2, jchar * val3)
void GetShortArrayRegion (jshortArray val0, jsize val1, jsize val2, jshort * val3)
void GetIntArrayRegion (jintArray val0, jsize val1, jsize val2, jint * val3)
void GetLongArrayRegion (jlongArray val0, jsize val1, jsize val2, jlong * val3)
void GetFloatArrayRegion (jfloatArray val0, jsize val1, jsize val2, jfloat * val3)
void GetDoubleArrayRegion (jdoubleArray val0, jsize val1, jsize val2, jdouble * val3)
参数的意义
表示把数组val1从第val个元素开始的val2个元素拷贝到val3地址所指向的内存区域
对象数组
如果你需要对java对象数组的对象进行操作,你必须使用GetObjectArrayElement函数以jobject形式返回数组的元素,然后再操作jobject
你也可以用SetObjectArrayElement函数把jobject放进java对象数组,另外GetArrayLength函数能返回数组的长度。
关于对jobject的操作请参照后文。
如果想返回一个新的数组,可以先用NewObjectArray函数创建一个Java数组本地C/C++类型对象数组,然后通过SetObjectArrayElement函数设置数组元素(java对象),最后返回直接返回该数组就可以了。
关于如何创建在JNI中创建一个jobject(Java对象)请参照后文。
jarray NewObjectArray (jsize val0, jclass cl1, jobject obj2)
第一参数,val0表示要创建的对象数组的大下。
第二参数,cl1表示创建的对象数组的元素是什么类型.
第三个参数,obj2表示数组元素的默认对象,一般用0表示null
jobject GetObjectArrayElement (jobjectArray val0, jsize val1)
参数意义
表示取得val0数组的第val1个元素
void SetObjectArrayElement (jobjectArray val0, jsize val1, jobject obj2)
参数意义
表示把val0数组的第val1个元素设置为对象obj2
另外,String数组当做一般的对象数组处理就可以了,只是String比普通的对象多了一些操作接口。
其实jstring只是jobject的一个子类,参看jni.h可知:
struct __jobject {};
struct __jclass : __jobject {};
struct __jstring : __jobject {};
示例
JNIEXPORT jobjectArray JNICALL Java_com_robin_HelloActivity_getStringArrayFromJni
(JNIEnv *env, jobject thiz)
{
jstring str;
jobjectArray args = 0;
jsize len = 5;
char* sa[] = { "Hello,", "world!", "This", "is", "robin" };
int i=0;
args = (env)->NewObjectArray(len,(env)->FindClass("java/lang/String"),0);
for( i=0; i < len; i++ )
{
str = (env)->NewStringUTF(sa[i] );
(env)->SetObjectArrayElement(args, i, str);
}
return args;
}

四、对象的传递和操作
JNI中,C函数名的java对象参数,除了String类外则都表示为jobject类型(String类表示为jstring类型).
JNI提供了在本地代码中操作Java对象的功能。
基本原理
首先需要找到对象属于哪个类(class).类(class)在JNI中用jclass进行表示。
查找java类有两种方式:
一、用类名(如android.os.Binder)FindClass()函数中查找得到jclass对象。
比如:jclass clazz = env->FindClass(kBinderPathName) ;
这里的kBinderPathName为"android.os.Binder",所以上面等同于jclass clazz= env->FindClass("android.os.Binder");
二、可以通过jclassGetObjectClass(jobject obj0)方法得到一个jclass,以表示obj0对象属于哪个类.
比如:jclass clazz=env->GetObjectClass (obj);
然后对Field和函数再分别按下面的两种方式进行处理。
成员变量:通过GetFieldID/GetStaticFieldID得Field的id(以jfieldID形式表示),然后调用GetXXXField/GetStaticXXXField函数就可以到Field的值,
调用SetXXXField/SetStaticXXXField就可以设置Field的值。
函数:和操作Field类似,通过GetMethodID/GetStaticMethodID得到函数的id(以jmethodID形式表示),然后就可以通过CallXXXMethod/CallStaticXXXMethod进行函数调用了.
GetFieldID/GetStaticFieldID简介
jfieldID GetFieldID (jclass cl0, const char * val1, const char * val2)
jfieldID GetStaticFieldID (jclass cl0, const char * val1, const char * val2)
前者用于非静态Field,后者用于静态Field
第一个参数,jcalss cl0用于表示在哪个类上进行操作。
第二个参数,const char * val对应函数的名字
第三个参数,const char * val2用于表示Field是什么类型.
关于java参数类型的符号化表示请参考JNI中java类型的符号化表示》
GetXXXField/GetStaticXXXField简介
GetXXXField/GetStaticXXXField中的XXX表示对什么类型的Field进行操作。前者用于非静态Field,后者用于静态Field。
该系列函数包括
非静态
jobject GetObjectField (jobject obj0, jfieldID fld1)
jboolean GetBooleanField (jobject obj0, jfieldID fld1)
jbyte GetByteField (jobject obj0, jfieldID fld1)
jchar GetCharField (jobject obj0, jfieldID fld1)
jshort GetShortField (jobject obj0, jfieldID fld1)
jint GetIntField (jobject obj0, jfieldID fld1)
jlong GetLongField (jobject obj0, jfieldID fld1)
jfloat GetFloatField (jobject obj0, jfieldID fld1)
jdouble GetDoubleField (jobject obj0, jfieldID fld1)
静态
jobject GetStaticObjectField (jclass cl0, jfieldID fld1)
jboolean GetStaticBooleanField (jclass cl0, jfieldID fld1)
jbyte GetStaticByteField (jclass cl0, jfieldID fld1)
jchar GetStaticCharField (jclass cl0, jfieldID fld1)
jshort GetStaticShortField (jclass cl0, jfieldID fld1)
jint GetStaticIntField (jclass cl0, jfieldID fld1)
jlong GetStaticLongField (jclass cl0, jfieldID fld1)
jfloat GetStaticFloatField (jclass cl0, jfieldID fld1)
jdouble GetStaticDoubleField (jclass cl0, jfieldID fld1)
第一个参数,jcalss cl0用于表示在哪个类上进行操作。
第二个参数,表示GetFieldID/GetStaticFieldID中得到的Field的id
SetXXXField/SetStaticXXXField简介
SetXXXField/SetStaticXXXField中的XXX表示对什么类型的Field进行操作。前者用于非静态Field,后者用于静态Field。
该系列函数包括
非静态
void SetObjectField (jobject obj0, jfieldID fld1, jobject obj2)
void SetBooleanField (jobject obj0, jfieldID fld1, jboolean val2)
void SetByteField (jobject obj0, jfieldID fld1, jbyte val2)
void SetCharField (jobject obj0, jfieldID fld1, jchar val2)
void SetShortField (jobject obj0, jfieldID fld1, jshort val2)
void SetIntField (jobject obj0, jfieldID fld1, jint val2)
void SetLongField (jobject obj0, jfieldID fld1, jlong val2)
void SetFloatField (jobject obj0, jfieldID fld1, jfloat val2)
void SetDoubleField (jobject obj0, jfieldID fld1, jdouble val2)
静态
void SetStaticObjectField (jclass cl0, jfieldID fld1, jobject obj2)
void SetStaticBooleanField (jclass cl0, jfieldID fld1, jboolean val2)
void SetStaticByteField (jclass cl0, jfieldID fld1, jbyte val2)
void SetStaticCharField (jclass cl0, jfieldID fld1, jchar val2)
void SetStaticShortField (jclass cl0, jfieldID fld1, jshort val2)
void SetStaticIntField (jclass cl0, jfieldID fld1, jint val2)
void SetStaticLongField (jclass cl0, jfieldID fld1, jlong val2)
void SetStaticFloatField (jclass cl0, jfieldID fld1, jfloat val2)
void SetStaticDoubleField (jclass cl0, jfieldID fld1, jdouble val2)
第一个参数,jcalss cl0用于表示在哪个类上进行操作。
第二个参数,表示GetFieldID/GetStaticFieldID中得到的Field的id
第三个参数,表示新值
GetMethodID/GetStaticMethodID简介
jmethodID GetMethodID (jclass cl0, const char * val1, const char * val2)
jmethodID GetStaticMethodID (jclass cl0, const char * val1, const char * val2)
调用他们能得到函数的id(以jmethodID形式表示).前者用于非静态Field,后者用于静态Field
第一个参数,jcalss cl0用于表示在哪个类上进行操作。
第二个参数,const char * val对应函数的名字
第三个参数,const char * val2用于表示函数的传入参数都有哪些,都是些什么类型,返回参数是什么类型。因为函数可以重载,所以必须要该参数才能定为函数。这里是以符号的形式表示传入参数的类型。
关于java参数类型的符号化表示请参考JNI中java类型的符号化表示》
CallXXXMethod/CallStaticXXXMethod
CallXXXMethod/CallStaticXXXMethod中的XXX表示对什么返回类型的Field函数进行调用。前者用于非静态函数的调用,后者用于静态函数的调用。
该系列函数包括
非静态
jobject CallObjectMethod (jobject obj0, jmethodID meth1, ...)
jboolean CallBooleanMethod (jobject obj0, jmethodID meth1, ...)
jbyte CallByteMethod (jobject obj0, jmethodID meth1, ...)
jchar CallCharMethod (jobject obj0, jmethodID meth1, ...)
jshort CallShortMethod (jobject obj0, jmethodID meth1, ...)
jint CallIntMethod (jobject obj0, jmethodID meth1, ...)
jlong CallLongMethod (jobject obj0, jmethodID meth1, ...)
jfloat CallFloatMethod (jobject obj0, jmethodID meth1, ...)
jdouble CallDoubleMethod (jobject obj0, jmethodID meth1, ...)
void CallVoidMethod (jobject obj0, jmethodID meth1, ...)
静态
jobject CallStaticObjectMethod (jclass cl0, jmethodID meth1, ...)
jboolean CallStaticBooleanMethod (jclass cl0, jmethodID meth1, ...)
jbyte CallStaticByteMethod (jclass cl0, jmethodID meth1, ...)
jchar CallStaticCharMethod (jclass cl0, jmethodID meth1, ...)
jshort CallStaticShortMethod (jclass cl0, jmethodID meth1, ...)
jint CallStaticIntMethod (jclass cl0, jmethodID meth1, ...)
jlong CallStaticLongMethod (jclass cl0, jmethodID meth1, ...)
jfloat CallStaticFloatMethod (jclass cl0, jmethodID meth1, ...)
jdouble CallStaticDoubleMethod (jclass cl0, jmethodID meth1, ...)
void CallStaticVoidMethod (jclass cl0, jmethodID meth1, ...)
第一个参数,jobject obj0表示调用哪个对象的非静态函数;jcalss cl0表示调用哪个类的静态函数。
第二个参数,jmethodID meth1表示调用函数的id(以jmethodID形式进行表示)
第三个及以后的参数,他们是调用的java函数的传入参数
如何返回一个新的JAVA对象呢?
通过NewObject函数创建一个java对象,然后像操作一般的对象一样操作它,最后返回该对象就可以了。
jobject NewObject (jclass cl0, jmethodID meth1, ...)
第一个参数,jclass cl0表示创建哪个类的实例。
第二个参数,jmethodID meth1表示用哪个构造函数创建该类的实例。
可用如下的方法得到构造函数的Id:
jclass strClass = env->FindClass("Ljava/lang/String;");
jmethodID ctorID = env->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V");
0 0