前言
JNI的基本使用,C中调用Java的成员变量,成员属性,构造方法,方法
提示:以下是本篇文章正文内容,下面案例可供参考
一、JNI的上下文?
大家知道在使用javah 编译class文件时会生成以下JNI关键字,如下。
JNIEXPORT void JNICALL Java_clz_Register_helloWord
(JNIEnv *, jobject);
1)JNIEnv
实际代表了Java环境,通过这个JNIEnv*指针,就可以对java断的代码进行操作,创建java对象,调用java对象的方法获取java对象的属性等。JNIEnv的指针会被JNI传人到本地方法的实现函数中来对java端的代码进行操作;
2)jobject obj
如果native方法不是static的话 ,**这个obj就代表这个nativa方法的实例
如果native方法是static的话 ,**这个obj就代表这个native方法的类的class的对象实例(static方法不需要实例,所以可以用来代表这个类的class对象)
3)JNIEXPORT和JNICALL都是JNI关键字,表示此函数是要被JNI调用的
二、C/C++调用Java
1)C/C++ 调用java很像java中的反射,在调用的过程中
GetFieldID/GetMethodID
GetStaticFieldID/GetStaticMethodID
下面看一个具体的方法
GetFieldID(jclass clazz ,const char* name , const char* sign)
方法的参数说明:
clazz:这个方法依赖的类对象的class对象
name:这个字段的名称
sign:这个字段的签名
2)调用Java代码签名问题
使用javap命令 javap -s -p xxx.class
数据类型 | 签名 |
---|---|
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | J |
float | F |
double | D |
void | V |
object | L/java/lang/XXX |
Array | 以[k开头加上元素类型的签名 例如 int[] 签名是[I |
3)实现Java调用native函数传入String在c中拼接返回给java使用,步骤如下
- 编写java代码
package clz;
public class Register {
static {
System.load("/Users/xxx/CLionProjects/libso1/cmake-build-debug/libxpmsLib.dylib");
}
static native String setPassword(String password);
public static void main(String[] args) {
Register register=new Register();
System.out.println(register.setPassword("123456"));
}
}
- 编写c代码
JNIEXPORT jstring JNICALL Java_clz_Register_setPassword
(JNIEnv * env, jobject j,jstring password){
// printf(password);
const char *c_p=NULL;
jboolean isCopy;
c_p=(*env)->GetStringUTFChars(env,password,&isCopy);
if(c_p==NULL){
return NULL;
}
char buf[128]={0};
///这里做字符串拼接
sprintf(buf,"home%s",c_p);
///*c_p是指针类型 释放内存
(*env)->ReleaseStringUTFChars(env,password,c_p);
return (*env)->NewStringUTF(env,buf);
}
4)异常处理
if(c_p==NULL){
return NULL;
}
GetStringUTFChars之后需要安全检查,JVM需要对新诞生的字符串内存空间,当内存空间不够分配时,会导致调用失败,GetStringUTFChars调用失败后回返回NULL,并抛出OutOfMemoryError异常,java遇到异常程序会停止运行,JNI晕倒错误程序仍然会继续运行,后续程序针对该空字符串的操作都是危险的,需要return立即结束当前方法。
5)释放字符串
在调用GetStringUTFChars函数从JVM内部获取一个字符串后,JVM内部会分配一个内存存储源字符串的拷贝,以便本地代码的访问和修改,当使用完后需要调用ReleaseStringUTFChars释放,每一个GetXXX会对应一个ReleaseXXX
6)C访问java的成员变量
先找类–>fieldID–>获取值
- java代码
package clz;
public class Register {
static {
System.load("/Users/xxx/CLionProjects/libso1/cmake-build-debug/libxpmsLib.dylib");
}
public int property=2;
public static int staticProperty=1008;
///非静态方法 JNI中使用GetObjectClass(env,j)获取 class对象
public native void getProperty();
///静态方法 JNI中使用 jobject 参数作为需要获取的class对象
public static native void getStaticProperty();
static native String setPassword(String password);
public static void main(String[] args) {
Register register=new Register();
// System.out.println(register.setPassword("123456"));
register.getProperty();
System.out.println(register.property);
getStaticProperty();
System.out.println(register.staticProperty);
}
}
- C代码
//获取非静态变量成员的数据
JNIEXPORT void JNICALL Java_clz_Register_getProperty(JNIEnv *env,jobject j){
//先取到类
jclass clazz=(*env)->GetObjectClass(env,j);
//先取到类ID
jfieldID jfId=(*env)->GetFieldID(env,clazz,"property","I");
//取值
jint jValue=(*env)->GetIntField(env,j,jfId);
printf(" property jValue : %d \n",jValue);
}
//修改非静态变量成员的数据
//获取非静态变量成员的数据
JNIEXPORT void JNICALL Java_clz_Register_getProperty(JNIEnv *env,jobject j){
//先取到类 getProperty是非static的 所以可以使用 GetObjectClass 传入jobject 获取
jclass clazz=(*env)->GetObjectClass(env,j);
//先取到类ID
jfieldID jfId=(*env)->GetFieldID(env,clazz,"property","I");
//取值
jint jValue=(*env)->GetIntField(env,j,jfId);
printf(" property jValue : %d \n",jValue);
///修改数据
//获取静态变量成员的数据 和修改数据
//获取静态变量成员的数据 和修改数据
JNIEXPORT void JNICALL Java_clz_Register_getStaticProperty(JNIEnv *env,jobject j){
//先取到类class getStaticProperty 是static 这里可以使用 FindClass获取 也可以直接只用jobject 参数
jclass clazz=(*env)->FindClass(env,"clz/Register");
// jclass clazz=(*env)->GetObjectClass(env,j);
//先取到类ID
// jfieldID jfId=(*env)->GetStaticFieldID(env,clazz,"staticProperty","I");///FindClass获取 clazz
jfieldID jfId=(*env)->GetStaticFieldID(env,j,"staticProperty","I");//也可以直接只用jobject 参数
//取值
jint jValue=(*env)->GetStaticIntField(env,j,jfId);
printf("static property jValue : %d \n",jValue);
///修改数据
(*env)->SetStaticIntField(env,j,jfId,11000);
}
7)JNI中FindClass和GetObjectClass的区别
FindClass是通过传java中完整的类名来查找java的class;
GetObjectClass是通过传入jni中的一个java的引用来获取该引用的类型。
8) JNI 调用 Java 类的方法(调用的是与native方法同一个类里的方法)
如果需要在JNI的C代码中调用Java方法,可以使用JNI提供的反射借口来反射得到Java方法,进行调用
- Java代码
package clz;
public class Register {
static {
System.load("/Users/xxx/CLionProjects/libso1/cmake-build-debug/libxpmsLib.dylib");
}
public int property=2;
public static int staticProperty=1008;
///非静态方法 JNI中使用GetObjectClass(env,j)获取 class对象
public native void getProperty();
///静态方法 JNI中使用 jobject 参数作为需要获取的class对象
public static native void getStaticProperty();
static native String setPassword(String password);
///测试JNI调用Java函数
public native void testJINCallJavaMethod();
public void methodTestJNICall(int a,String value ){
System.out.println(" a == "+a+ " value ="+value);
}
public static void main(String[] args) {
Register register=new Register();
// System.out.println(register.setPassword("123456"));
// register.getProperty();
// System.out.println(register.property);
// getStaticProperty();
// System.out.println(register.staticProperty);
register.testJINCallJavaMethod();
}
}
- C代码
///测试调用 Register 类中的 methodTestJNICall(int a,String value)方法
JNIEXPORT void JNICALL Java_clz_Register_testJINCallJavaMethod(JNIEnv *env,jobject obj){
///1 先获取TestJNICallJavaMethod类的class
jclass clazz=(*env)->FindClass(env,"clz/Register");
if(clazz==NULL){
return;
}
///2 声明传入的string参数
jstring jstr=(*env)->NewStringUTF(env," this is string");
///3 获取id jfId
jmethodID jfId=(*env)->GetMethodID(env,clazz,"methodTestJNICall","(ILjava/lang/String;)V");
if(jfId==0){
return;
}
///4 方法调用
(*env)->CallVoidMethod(env,obj,jfId,11,jstr);
(*env)->DeleteLocalRef(env,clazz);
(*env)->DeleteLocalRef(env,jstr);
}
9)调用Java构造方法,并执行相应对象的实例方法
- java代码
package clz;
//需要调用的构造方法/普通方法的类代码
public class TestJNICallJavaMethod {
private int property=3;
TestJNICallJavaMethod(int property){
this.property=property;
System.out.println(" property == "+property);
}
public void methodTestJNICall(int a ,String value){
System.out.println(" a == "+a+ " value ="+value);
}
}
package clz;
///执行程序的入口调用
public class Register {
static {
System.load("/Users/xxx/CLionProjects/libso1/cmake-build-debug/libxpmsLib.dylib");
}
public int property=2;
public static int staticProperty=1008;
///非静态方法 JNI中使用GetObjectClass(env,j)获取 class对象
public native void getProperty();
///静态方法 JNI中使用 jobject 参数作为需要获取的class对象
public static native void getStaticProperty();
static native String setPassword(String password);
///测试JNI调用本类的Java函数
public native void testJINCallJavaMethod();
///测试JNI调用其他类的 Java函数
public native void testOtherJINCallJavaMethod();
public void methodTestJNICall(int a,String value ){
System.out.println(" a == "+a+ " value ="+value);
}
public static void main(String[] args) {
Register register=new Register();
// System.out.println(register.setPassword("123456"));
// register.getProperty();
// System.out.println(register.property);
// getStaticProperty();
// System.out.println(register.staticProperty);
// register.testJINCallJavaMethod();
register.testOtherJINCallJavaMethod();
}
}
- C代码
///测试调用 TestJNICallJavaMethod 类中的 methodTestJNICall(int a,String value)方法
JNIEXPORT void JNICALL Java_clz_Register_testOtherJINCallJavaMethod(JNIEnv *env,jobject obj){
///1 先获取TestJNICallJavaMethod类的class
jclass clazz=(*env)->FindClass(env,"clz/TestJNICallJavaMethod");
if(clazz==NULL){
return;
}
///2 声明传入的string参数
jstring jstr=(*env)->NewStringUTF(env," this is string");
///3 获取 构造方法的 id jfId 这里传如的方法名称是 <init>
jmethodID jfId=(*env)->GetMethodID(env,clazz,"<init>","(I)V");
if(jfId==0){
return;
}
/// 4.初始化构造函数(此处传递了参数)
jobject jniObj=(*env)->NewObject(env,clazz,jfId,290);
/// 5.获取 TestJNICallJavaMethod类的methodTestJNICall的jmethodID
jmethodID jfIdOther=(*env)->GetMethodID(env,clazz,"methodTestJNICall","(ILjava/lang/String;)V");
///6 方法调用
(*env)->CallVoidMethod(env,jniObj,jfIdOther,11,jstr);
(*env)->DeleteLocalRef(env,clazz);
(*env)->DeleteLocalRef(env,jstr);
}