前言
前面阐述了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 stub
System.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.class
jclass 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.class
jclass 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);
}
}
最后的运行结果: