JNI编程二:JNI数据类型


前言

前面阐述了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);
    }

}

最后的运行结果:
在这里插入图片描述

  • 14
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值