在进行JNI开发之前,我们必须要了解下面一些东西,首先JNI开发的流程,再然后我们需要了解一下Native和Java中类型映射关系,以及JVM中已经为我们提供的一些常用JNI函数,最后再了解一下C/C++开发的一些注意事项。
JNI开发流程
-
编写包含Native方法的Java代码
-
编译Java代码(javac)
-
创建C/C++头文件(javah)
-
编写C/C++代码
-
创建库文件
# gcc -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -std=c++11 -shared -o opt_jni.so My.cpp
原始类型和Native类型映射
Java Type | Native Type | Description |
---|---|---|
boolean | jboolean | unsigned 8 bits |
byte | jbyte | signed 8 bits |
char | jchar | unsigned 16 bits |
short | jshort | signed 16 bits |
int | jint | signed 32 bits |
long | jlong | signed 64 bits |
float | jfloat | 32 bits |
double | jdouble | 64 bits |
void | void | N/A |
引用类型映射
typedef struct _jobject *jobject;
typedef jobject jclass;
typedef jobject jthrowable;
typedef jobject jstring;
typedef jobject jarray;
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 jarray jobjectArray;
类型签名
Type Signature | Java Type |
---|---|
Z | boolean |
B | byte |
C | char |
S | short |
I | int |
J | long |
F | float |
D | double |
L fully-qualified-class ; | fully-qualified-class |
V | void |
[ type | type[] |
( arg-types ) ret-type | method type |
例如Java方法:
long f (int n, String s, int[] arr);
在Native层面对应的类型签名为:
(ILjava/lang/String;[I)J
()
内为方法的参数类型签名,()
外为方法方法返回类型。类型签名在函数重载时就会变得很有用。
常用JNI函数
类操作
//获取Java类
jclass FindClass(JNIEnv *env, char *className) ;
//确定clazz1的对象是否可以安全地转换为clazz2
jboolean IsAssignableFrom(JNIEnv *env, jclass clazz1,jclass clazz2);
数组操作
//返回数组长度
jsize GetArrayLength(JNIEnv *env, jarray array);
// 创建对象数组
jobjectArray NewObjectArray(JNIEnv *env, jsize length, jclass elementClass, jobject initialElement);
// 构建基础数组对象,返回jbooleanArray、jbyteArray、jintArray...
ArrayType New<PrimitiveType>Array(JNIEnv *env, jsize length);
//设置Object数组元素
void SetObjectArrayElement(JNIEnv *env, jobjectArray array,jsize index, jobject value);
// 通知VM本机代码不再需要访问elems
void Release<PrimitiveType>ArrayElements(JNIEnv *env,ArrayType array, NativeType *elems, jint mode);
//返回数组元素的指针
NativeType *Get<PrimitiveType>ArrayElements(JNIEnv *env,ArrayType array, jboolean *isCopy);
//释放数组使用的内存
ReleaseIntArrayElements(JNIEnv *env,jintArray array,int *elems,mode);
//数组拷贝,从buf从复制到array
void Set<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array,jsize start, jsize len, const NativeType *buf);
对象操作函数
//在不调用对象的任何构造函数的情况下分配新的Java对象
jobject AllocObject(JNIEnv *env, jclass clazz);
//将所有要传递给构造函数的参数紧跟在methodID参数之后。NewObject()接受这些参数并将它们传递给希望调用的Java方法
jobject NewObject(JNIEnv *env, jclass clazz,jmethodID methodID, ...);
//功能同NewObject,但参数放在数组中
jobject NewObjectA(JNIEnv *env, jclass clazz,jmethodID methodID, const jvalue *args);
//功能同NewObject,但参数放在va_list中
jobject NewObjectV(JNIEnv *env, jclass clazz,jmethodID methodID, va_list args);
// 返回字段ID
jfieldID GetFieldID(JNIEnv *env, jclass clazz,const char *name, const char *sig);
// 返回字段的内容
NativeType Get<type>Field(JNIEnv *env, jobject obj,jfieldID fieldID);
//设置字段内容,如SetObjectField、SetIntField
void Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID,NativeType value);
//返回类或接口实例(非静态)方法的ID
jmethodID GetMethodID(JNIEnv *env, jclass clazz,const char *name, const char *sig);
// 调用指定返回类型的方法,如CallVoidMethod、CallObjectMethod、CallLongMethod
NativeType Call<type>Method(JNIEnv *env, jobject obj,jmethodID methodID, ...);
字符串操作
//返回字符串长度
jsize GetStringLength(JNIEnv *env, jstring string);
//返回指向字符串的Unicode字符数组的指针
const jchar * GetStringChars(JNIEnv *env, jstring string,jboolean *isCopy);
//通知JVM不再使用该字符指针
void ReleaseStringChars(JNIEnv *env, jstring string,const jchar *chars);
//返回UTF8编码表示的字符数组指针
const char * GetStringUTFChars(JNIEnv *env, jstring string,jboolean *isCopy);
// 通知JVM不再使用该字符数组指针
void ReleaseStringUTFChars(JNIEnv *env, jstring string,const char *utf);
其它
//
jint ThrowNew(JNIEnv *env, jclass clazz,const char *message);
更多可以参考:JNI Functions
开发说明
语法
在进行native开发时,我们需要调用native库中提供的方法,对于c和c++调用语法是存在区别:
# C 语法
cls = (*env)->FindClass(env, "Sample2");
# C++ 语法
cls = env->FindClass("Sample2");
归其原因主要是c是面向过程,c++是面向对象语言。
C++对象初始化
A a = A(); //code1
A* a=new A();//code2
上面两行代码的区别在于,code1在栈上开辟内存,code2是在堆上开辟内存,所以对应code2,我们在对象不再使用后,用做内存的手动释放,即delete a;
*
和&
的区别
C/C++开发,可能最让人头疼的就是指针和引用了,所以在JNI开发时,我们也回避不了这个问题,所以我们还是要复习一下这两个概念:指针是一个变量指向的是另外一个变量地址,而引用相当于给变量起了一个别名。
例如:
int i; // 声明简单的变量
int *ip; // 指针变量的声明
int& r = i;// 声明引用变量
ip = &i // 在指针变量中存储 var 的地址
cout << ip << endl;//打印的是指向的地址
cout << *ip << endl;//打印的是指向地址中的内容
从上面我们发现不管*
还是&
,都有两种概念,第一他们本身可以作为指针/引用的声明,第二,对于*ip
我们可以看作是取值操作,而对于&var
我们可以看作这是一个取址操作,他获取的是变量的地址。
说到这,再多补充一点吧,C/C++函数定义时,有些参数定义为指针类型参数、有些定义成引用类型的参数,例如:
void foo1(int* param)
void foo2(int& param)
其实不管是指针传参还是引用传参,相同的是他们都可以改变实参的内容,不同的是:
- 指针可以指向NULL,而引用不能引用NULL
- 指针可以随时改变指向的对象,而引用一旦被定义就不能改变引用关系
当然还有一个直观的区别,在编码时我们就可以发现:
int i = 10;
fool(&i);
foo2(i);
貌似定义为引用时,代码看起来更加优雅。
线程安全
推荐:
env->MonitorEnter(obj);
/**
同步
*/
env->MonitorExit(obj);
其它方法:
直接在Java的native方法定义上加synchronized
限定。