目录
- 前言
- 一、数据类型 jclass / jobject
- 二、JNI常见的数据类型
- 三、运用数据类型
- 3.1 修改String类型的变量
- 3.2 修改int类型的变量
前言
前面阐述了JNI的开发流程,接下来探究JNI中的数据类型。编码承接上文JNI编程一:JNI开发流程
一、数据类型 jclass / jobject
在jni里面会有两个这样的数据类型jclass / jobject,顾名思义它们分别对应java里面的 Class和Object。
现在我们来编写对应的代码,之前创建的java工程里面有一个类JniMain.java,声明了第一个native方法。
public class JniMain {//静态方法public native static String getStringFromC();static{System.loadLibrary("JNI_Demo1");}public static void main(String[] args) {// TODO Auto-generated method stubSystem.out.println(getStringFromC());}
}
声明的 public native static String getStringFromC() 这个方法是一个静态方法,这也就意味着我们可以不需要创建JniMain类对象而直接调用。
那么再来看看通过javah编译生成的JniMain.h文件
#include "jni.h"#ifndef _Included_JniMain
#define _Included_JniMain
#ifdef __cplusplus
extern "C" {
#endif
/** Class: JniMain* Method: getStringFromC* Signature: ()Ljava/lang/String;*/
JNIEXPORT jstring JNICALL Java_JniMain_getStringFromC (JNIEnv *, jclass);#ifdef __cplusplus
}
#endif
#endif
可以看到Java_JniMain_getStringFromC(JNIEnv*,jclass)函数里面有一个参数是jclass类型的。
再来看看实现JniMain.h文件函数的jni_impl.c
#include "stdafx.h"#include "JniMain.h"JNIEXPORT jstring JNICALL Java_JniMain_getStringFromC (JNIEnv * env, jclass jclz){return (*env)->NewStringUTF(env, "hello JNI");
}
函数JNICALL Java_JniMain_getStringFromC (JNIEnv * env, jclass jclz)里面的第二个参数是jclass类型,形参jclz就是对应的JniMain.class对象。
也就是说当我们在java文件里面编写native方法是一个static修饰的方法,那么我们jni头文件里面对应的方法就会添加一个jclass类型的形参。
那么在java文件里面编写native方法是一个非static修饰的方法还会有jclass参数吗?
接下来在JniMain.java里面编写一个非静态native方法 String getStringFromC2()
public class JniMain {//静态方法public native static String getStringFromC();//非静态方法public native String getStringFromC2();
}
再来看一看编译生成的JniMain.h文件
#include <jni.h>#ifndef _Included_JniMain
#define _Included_JniMain
#ifdef __cplusplus
extern "C" {
#endif
/** Class: JniMain* Method: getStringFromC* Signature: ()Ljava/lang/String;*/
JNIEXPORT jstring JNICALL Java_JniMain_getStringFromC(JNIEnv *, jclass);/** Class: JniMain* Method: getStringFromC2* Signature: ()Ljava/lang/String;*/
JNIEXPORT jstring JNICALL Java_JniMain_getStringFromC2(JNIEnv *, jobject);#ifdef __cplusplus
}
#endif
#endif
这个时候Java_JniMain_getStringFromC2(JNIEnv *, jobject)函数的形参就变成了jobject类型的了。
接下来我们再来看看jni_impl.c里面对该函数的实现
#include "stdafx.h"#include "JniMain.h"JNIEXPORT jstring JNICALL Java_JniMain_getStringFromC
(JNIEnv * env, jclass jclz){return (*env)->NewStringUTF(env, "hello JNI");
}JNIEXPORT jstring JNICALL Java_JniMain_getStringFromC2
(JNIEnv * env, jobject jobj){return (*env)->NewStringUTF(env, "I'm stonger");
}
可以看到Java_JniMain_getStringFromC2(JNIEnv * env, jobject jobj)函数里面的形参是jobject类型的,形参jobj就是对应的java里面的JniMain对象。
最后我们将c工程再编译生成一个JNI_Demo1.dll动态库,将它放到java工程里面运行。
附上JniMain.java调用动态库的代码
public class JniMain {//静态方法public native static String getStringFromC();//非静态方法public native String getStringFromC2();static{System.loadLibrary("JNI_Demo1");}public static void main(String[] args) {//调用静态native方法System.out.println(getStringFromC());//调用非静态native方法JniMain jm = new JniMain();System.out.println(jm.getStringFromC2());}}
运行结果如下
二、JNI常见的数据类型
现在java JNI c所对应的基本数据类型
java JNI c
基本数据类型:
boolean jboolean unsigned char
byte jbyte signed char
char jchar signed char
short jshort short
int jint int
long jlong long long
float jfloat float
double double double
引用类型:
String jstring
Object jobject
基本数据类型数组:
byte[] jByteArray
引用数据类型数组:
Object[] jobjectArray
String[] jobjectArray
三、运用数据类型
接下来,咱们来运用运用jin数据类型。
3.1 修改String类型的变量
举个例子,在java代码中有一个String类型的变量,我们通过jni去修改这个变量。
现在我们开始写java代码,JniMain里面声明了key字符串和accessFieldModify()方法
public class JniMain {//一个全局的字符串public String key = "key";//修改key的方法public native String accessFieldModify();public static void main(String[] args) {}}
我们用javah编译JniMain.java,将生成好的JniMain.h放到c工程里面,下面展示JniMain.h代码
#include <jni.h>#ifndef _Included_JniMain
#define _Included_JniMain
#ifdef __cplusplus
extern "C" {
#endif
/** Class: JniMain* Method: accessFieldModity* Signature: ()Ljava/lang/String;*/
JNIEXPORT jstring JNICALL Java_JniMain_accessFieldModity(JNIEnv *, jobject);#ifdef __cplusplus
}
#endif
#endif
接下来我们就在jni_impl.c文件里面实现这个Java_JniMain_accessFieldModity(JNIEnv *, jobject)方法,这个方法的功能就是修改JniMain.java里面全局变量key的值。实现代码如下
#include "stdafx.h"#include "JniMain.h"
#include <string.h>//访问java中非静态全局变量key,并修改值
JNIEXPORT jstring JNICALL Java_JniMain_accessFieldModity
(JNIEnv * env, jobject jobj){//得到jclass,即JniMain.classjclass jclz = (*env)->GetObjectClass(env,jobj);//得FieldId, "key" 属性名称,"Ljava/lang/String;"属性签名jfieldID fid = (*env)->GetFieldID(env,"jclz", "key", "Ljava/lang/String;");//得到key对应的值jstring jstr = (*env)->GetObjectField(env,jobj,fid);//将jstring类型转化为c语言的char字符数组char * c_str = (*env)->GetStringUTFChars(env,jstr,NULL);//将字符串"key"改成"hello key"char text[20] = "hello ";strcat(text, c_str);jstring new_str = (*env)->NewStringUTF(env,text);//给jobj的key成员变量设置新的值(*env)->SetObjectField(env,jobj,fid,new_str);//释放内存(*env)->ReleaseStringUTFChars(env,new_str,c_str);return new_str;
}
上述代码中的jni方法太纠结,这些方法的作用会在以后的博客中介绍到,现在只需要结合注释明白Java_JniMain_accessFieldModity方法里面的实现过程即可。其中获取域jfieldID的方法有个属性签名"Ljava/lang/String;",这个签名是标识这个域在在java里面对应的什么数据类型,"Ljava/lang/String;"就代表的是String类型。
关于属性前面有一个对应的表,在平时编写jni时对应查阅一下就行了。
编写完jni代码之后就生成对应的.dll动态库(如何生成动态库,在上一篇博客里面有讲到),再将动态库放到java工程里面调用。
贴出JniMain.java里面的调用代码:
public class JniMain {//一个全局的字符串public String key = "key";//修改key的方法public native String accessFieldModify();static{System.loadLibrary("JNI_Demo1");}public static void main(String[] args) {//调用非静态native方法JniMain jm = new JniMain();System.out.println("change before key: "+jm.key);jm.accessFieldModify();System.out.println("after change key: "+jm.key);}}
再来看看运行结果,成功修改了成员变量key的值
3.2 修改int类型的变量
再举个例子,在java里面定义一个int类型的静态成员变量,然后通过jni去修改这个变量。
首先编写我们的JniMain.java代码
public class JniMain {public static int count = 2;public native void accessStaticFieldModify();
}
接下来,生成JniMain.h,并把JniMain.h文件放到c工程里面
#include "jni.h"#ifndef _Included_JniMain
#define _Included_JniMain
#ifdef __cplusplus
extern "C" {
#endif/** Class: JniMain* Method: accessStaticFieldModify* Signature: ()V*/
JNIEXPORT void JNICALL Java_JniMain_accessStaticFieldModify(JNIEnv *, jobject);#ifdef __cplusplus
}
#endif
#endif
然后再在jni_impl.c里面实现Java_JniMain_accessStaticFieldModify(JNIEnv *, jobject)函数。
#include "stdafx.h"#include "JniMain.h"
#include <string.h>JNIEXPORT void JNICALL Java_JniMain_accessStaticFieldModify
(JNIEnv * env, jobject jobj){//得到jclass,即JniMain.classjclass jclz = (*env)->GetObjectClass(env,jobj);//得FieldId, "count" 属性名称,"I"属性签名jfieldID fid = (*env)->GetStaticFieldID(env, jclz, "count", "I");//获取count的值jint count = (*env)->GetStaticIntField(env,jclz,fid);count += 5;//给jobj的count静态成员变量设置新的值(*env)->SetStaticIntField(env, jclz, fid, count);}
最后编译生成.dll动态库,并把动态库放到java工程里面。
接下来加载动态库并调用,代码如下
public class JniMain {public static int count = 2;public native void accessStaticFieldModify();static{System.loadLibrary("JNI_Demo1");}public static void main(String[] args) {//调用非静态native方法JniMain jm = new JniMain();System.out.println("change before count: " + count);jm.accessStaticFieldModify();System.out.println("after change count: " + count);}}
最后的运行结果: