(二)JNI 的数据类型以及C访问Java属性和方法

温馨提示

写博客是为了记录在开发过程中所涉及到的技术以及遇到的问题的解决,如果该博客对您有所帮助,希望可以点个关注/喜欢;如果您对文章中的内容有什么不同的见解,欢迎留言进行讨论。谢谢!

JNI 的数据类型

一、JNI 数据类型

Java基本数据类型与JNI数据类型的映射关系

Java数据类型JNI数据类型C/C++数据类型
booleanjbooleanunsigned char
bytejbytesigned char
charjcharunsigned short
shortjshortshort
intjintint
longjlonglong long
floatjfloatfloat
doublejdoubledouble
voidvoidvoid

Java引用数据类型与JNI数据类型的映射关系

Java数据类型JNI数据类型
Stringjstring
Objectjobject
基本数据类型数组如: byte[]jbyteArray
对象数组如: Object[],String[]jobjectArray

二、C通过JNI调用Java

1、访问Java属性

GET<Type>Field

  • 编写JNITest.java文件
public class JNITest {
    public String key = "World!";
    //访问属性,返回修改之后的属性内容
    public native String accessField();

    public static void main(String[] args){
        JNITest t = new JNITest();
        System.out.println("key修改前:"+t.key);
        t.accessField();
        System.out.println("key修改后:"+t.key);
    }

    static {
        System.loadLibrary("JNIProject");
    }
}
  • 在com_example_jni_JNITest.h文件声明对应的方法
/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"
/* Header for class com_example_jni_JNITest */

#ifndef _Included_com_example_jni_JNITest
#define _Included_com_example_jni_JNITest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_jni_JNITest
 * Method:    accessField
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_jni_JNITest_accessField
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif

  • 在JNITest.c文件实现com_example_jni_JNITest.h中声明的方法
#include "com_example_jni_JNITest.h"

JNIEXPORT jstring JNICALL Java_com_example_jni_JNITest_accessField
(JNIEnv *jEnv, jobject jobj) {
    //jobj是t对象,JniTest.class
    jclass cls = (*jEnv)->GetObjectClass(jEnv, jobj);
    //jfieldID
    //属性名称,属性签名
    jfieldID fid = (*jEnv)->GetFieldID(jEnv, cls, "key", "Ljava/lang/String;");

    //World! >> Hello World!
    //获取key属性的值
    //Get<Type>Field
    jstring jstr = (*jEnv)->GetObjectField(jEnv, jobj, fid);
    printf("jstr:%#x\n", &jstr);

    //jstring -> c字符串
    //isCopy 是否复制(true代表复制,false不复制)
    char *c_str = (*jEnv)->GetStringUTFChars(jEnv, jstr, NULL);
    //拼接得到新的字符串
    char text[20] = "Hello ";
    strcat(text, c_str);

    //c字符串 ->jstring
    jstring new_jstr = (*jEnv)->NewStringUTF(jEnv, text);

    //修改key
    //Set<Type>Field
    (*jEnv)->SetObjectField(jEnv, jobj, fid, new_jstr);

    printf("new_jstr:%#x\n", &new_jstr);

    return new_jstr;
}
  • 将c代码生成解决文件(动态库.dll)
  • 将动态库文件拷贝到java项目根路径下
  • 运行JNITest.java ,即可看到输出结果。

2、访问Java静态属性

GET<Type>Field

  • 编写JNITest.java文件
public class JNITest {
    public static int count = 5;
    //访问属性,返回修改之后的属性内容
    public native int accessStaticField();

    public static void main(String[] args){
        JNITest t = new JNITest();
        System.out.println("count 修改前:"+t.count);
        t.accessStaticField();
        System.out.println("count 修改后:"+t.count);
    }

    static {
        System.loadLibrary("JNIProject");
    }
}
  • 在com_example_jni_JNITest.h文件声明对应的方法
/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"
/* Header for class com_example_jni_JNITest */

#ifndef _Included_com_example_jni_JNITest
#define _Included_com_example_jni_JNITest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_jni_JNITest
 * Method:    accessField
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jint JNICALL Java_com_example_jni_JNITest_accessStaticField
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif

  • 在JNITest.c文件实现com_example_jni_JNITest.h中声明的方法
#include "com_example_jni_JNITest.h"

JNIEXPORT jint JNICALL Java_com_example_jni_JNITest_accessStaticField
(JNIEnv *jEnv, jobject jobj) {
    //jobj是t对象,JniTest.class
    jclass cls = (*jEnv)->GetObjectClass(jEnv, jobj);
    //jfieldID
    //属性名称,属性签名
    jfieldID fid = (*jEnv)->GetStaticFieldID(jEnv, cls, "count", "I");

    //获取 count 属性的值
    //Get<Type>Field
    jint count = (*jEnv)->GetStaticIntField(jEnv, jobj, fid);
    printf("jstr:%#x\n", &jstr);
    count++;
    (*jEnv)->SetStaticIntField(jEnv, jobj, fid,count);
    return count;
}
  • 将c代码生成解决文件(动态库.dll)
  • 将动态库文件拷贝到java项目根路径下
  • 运行JNITest.java ,即可看到输出结果。

3、访问java方法

Call<Type>Method

  • 编写JNITest.java文件
public class JNITest {
  
    public native void accessMethod();

    public static void main(String[] args){
        JNITest t = new JNITest();
        t.accessMethod();
    }

    //产生指定范围的随机数
    public int genRandomInt(int max){
        System.out.println("genRandomInt 执行了...");
        return new Random().nextInt(max); 
    }
    
    static {
        System.loadLibrary("JNIProject");
    }
}
  • 在com_example_jni_JNITest.h文件声明对应的方法;签名可通过下面的命令获取
javap -s -p com.example.jni.JNITest

产生的结果中有对应的属性和方法的签名

/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"
/* Header for class com_example_jni_JNITest */

#ifndef _Included_com_example_jni_JNITest
#define _Included_com_example_jni_JNITest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_jni_JNITest
 * Method:    accessMethod
 * Signature: (I)I;
 */
JNIEXPORT void JNICALL Java_com_example_jni_JNITest_accessMethod
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif

  • 在JNITest.c文件实现com_example_jni_JNITest.h中声明的方法
#include "com_example_jni_JNITest.h"

JNIEXPORT void JNICALL Java_com_example_jni_JNITest_accessMethod
(JNIEnv *env, jobject jobj){
    //jclass
    jclass cls = (*env)->GetObjectClass(env, jobj);
    //jmethodID
    jmethodID mid = (*env)->GetMethodID(env, cls, "genRandomInt", "(I)I");
    //调用
    //Call<Type>Method
    jint random = (*env)->CallIntMethod(env, jobj, mid, 200);
    printf("random num:%ld",random);

    //.....
}
  • 将c代码生成解决文件(动态库.dll)
  • 将动态库文件拷贝到java项目根路径下
  • 运行JNITest.java ,即可看到输出结果。

4、访问静态方法

CallStatic<Type>Method

  • 编写JNITest.java文件
public class JNITest {
  
    public native void accessStaticMethod();

    public static void main(String[] args){
        JNITest t = new JNITest();
        t.accessStaticMethod();
    }

    //产生UUID字符串
    public static String getUUID(){
        return UUID.randomUUID().toString();
    }
    
    static {
        System.loadLibrary("JNIProject");
    }
}
  • 在com_example_jni_JNITest.h文件声明对应的方法;签名可通过下面的命令获取
javap -s -p com.example.jni.JNITest

产生的结果中有对应的属性和方法的签名

/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"
/* Header for class com_example_jni_JNITest */

#ifndef _Included_com_example_jni_JNITest
#define _Included_com_example_jni_JNITest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_jni_JNITest
 * Method:    accessStaticMethod
 * Signature: ()Ljava/lang/String;;
 */
JNIEXPORT jstring JNICALL Java_com_example_jni_JNITest_accessStaticMethod
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif

  • 在JNITest.c文件实现com_example_jni_JNITest.h中声明的方法
#include "com_example_jni_JNITest.h"

JNIEXPORT jstring JNICALL Java_com_example_jni_JNITest_accessStaticMethod
(JNIEnv *env, jobject jobj){
    //jclass
    jclass cls = (*env)->GetObjectClass(env, jobj);
    //jmethodID 
    jmethodID mid = (*env)->GetStaticMethodID(env, cls, "getUUID", "()Ljava/lang/String;");
    
    //调用
    //CallStatic<Type>Method
    jstring uuid = (*env)->CallStaticObjectMethod(env, cls, mid);

    //随机文件名称 uuid.txt
    //jstring -> char*
    //isCopy ,代表java和c操作的是同一个字符串
    char *uuid_str = (*env)->GetStringUTFChars(env, uuid, NULL);
    //拼接
    char filename[100];
    sprintf(filename, "D://%s.txt",uuid_str);
    FILE *fp = fopen(filename,"w");
    fputs("How are you?", fp);
    fclose(fp);
}

  • 将c代码生成解决文件(动态库.dll)
  • 将动态库文件拷贝到java项目根路径下
  • 运行JNITest.java ,即可看到输出结果。

5、访问构造方法

通过FindClass找到对应的类

  • 编写JNITest.java文件
public class JNITest {
    //java.util.Date
    public native Date accessConstructor();

    public static void main(String[] args){
        JNITest t = new JNITest();
        t.accessConstructor();
    }

    
    static {
        System.loadLibrary("JNIProject");
    }
}
  • 在com_example_jni_JNITest.h文件声明对应的方法;签名可通过下面的命令获取
javap -s -p com.example.jni.JNITest

产生的结果中有对应的属性和方法的签名

/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"
/* Header for class com_example_jni_JNITest */

#ifndef _Included_com_example_jni_JNITest
#define _Included_com_example_jni_JNITest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_jni_JNITest
 * Method:    accessConstructor
 * Signature: 
 */
JNIEXPORT jobject JNICALL Java_com_example_jni_JNITest_accessConstructor
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif

  • 在JNITest.c文件实现com_example_jni_JNITest.h中声明的方法
#include "com_example_jni_JNITest.h"

//使用java.util.Date产生一个当前的时间戳
JNIEXPORT jobject JNICALL Java_com_example_jni_JNITest_accessConstructor
(JNIEnv *env, jobject jobj){
    jclass cla = (*env)->FindClass(env,"java/util/Date");
    //jmethodID,构造方法的方法名称固定为"<init>"
    jmethodID constructor_mid = (*env)->GetMethodID(env,cls,"<init>","()V");
    //实例化一个Date对象
    jobject date_obj = (*env)->NewObject(env,cls,constructor_mid);
    //调用getTime方法
    jmehoodID mid = (*env)->GetMethodID(env,cls,"getTime","()J");
    //调用
    jlong time = 
    (*env)->CallLongMethod(env,date_obj,mid);
    //time 为long long类型
    printf("time: %lld\n",time);
    return date_obj;
}

  • 将c代码生成解决文件(动态库.dll)
  • 将动态库文件拷贝到java项目根路径下
  • 运行JNITest.java ,即可看到输出结果。

6、访问父类的方法

  • 编写父类文件
public class Human {
    public void sayHi(){
        System.out.println("Human say Hi...");
    }
}


public class man extends Human{
    @override
    public void sayHi(){
        System.out.println("Man say Hi,How are you?");
    }
}
  • 编写JNITest.java文件
public class JNITest {
    public Human human = new Man();
    public native void accessNonvirtualMethod();

    public static void main(String[] args){
        JNITest t = new JNITest();
        t.accessNonvirtualMethod();
    }

    
    static {
        System.loadLibrary("JNIProject");
    }
}
  • 在com_example_jni_JNITest.h文件声明对应的方法;签名可通过下面的命令获取
javap -s -p com.example.jni.JNITest

产生的结果中有对应的属性和方法的签名

/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"
/* Header for class com_example_jni_JNITest */

#ifndef _Included_com_example_jni_JNITest
#define _Included_com_example_jni_JNITest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_jni_JNITest
 * Method:    accessNonvirtualMethod
 * Signature: 
 */
JNIEXPORT jobject JNICALL Java_com_example_jni_JNITest_accessNonvirtualMethod
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif

  • 在JNITest.c文件实现com_example_jni_JNITest.h中声明的方法
#include "com_example_jni_JNITest.h"

//调用父类的方法 
JNIEXPORT jobject JNICALL Java_com_example_jni_JNITest_accessNonvirtualMethod
(JNIEnv *env, jobject jobj){
    jclass cls = (*env)->GetObjectClass(env,jobj);
    //获取Man属性
    jfieldID fid =  (*env)->GetFieldID(env,cls,"human","Lcom/example/jni/Human;");
    jobject human_obj = (*env)->GetObjectField(env,jobj,fid);
    //执行sayHi方法
    //传父类的父类
    jclass human_cls = (*env)->FindClass(env,"com/example/jni/Human");
    jmethodID  mid = (*env)->GetMethodID(env,human_cls,"sayHi","()V");
    //执行子类方法
    (*env)->CallObjectMethod(env,human_obj,mid);
    
    //执行父类方法
    (*env)->CallNonvirtualObjectMethod(env,human_obj,human_cls,mid);
    
}

  • 将c代码生成解决文件(动态库.dll)
  • 将动态库文件拷贝到java项目根路径下
  • 运行JNITest.java ,即可看到输出结果。

三、JNI字符串乱码问题

  • 编写JNITest.java文件
public class JNITest {
    
    public native String chineseChars(String in);

    public static void main(String[] args){
        JNITest t = new JNITest();
        String outStr = t.chineseChars("传入一串中文");
        System.out.println(outStr);
    }

    
    static {
        System.loadLibrary("JNIProject");
    }
}
  • 在com_example_jni_JNITest.h文件声明对应的方法;签名可通过下面的命令获取
javap -s -p com.example.jni.JNITest

产生的结果中有对应的属性和方法的签名

/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"
/* Header for class com_example_jni_JNITest */

#ifndef _Included_com_example_jni_JNITest
#define _Included_com_example_jni_JNITest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_jni_JNITest
 * Method:    chineseChars
 * Signature: 
 */
JNIEXPORT jstring JNICALL Java_com_example_jni_JNITest_chineseChars
(JNIEnv *, jobject,jstring);
#ifdef __cplusplus
}
#endif
#endif

  • 在JNITest.c文件实现com_example_jni_JNITest.h中声明的方法
#include "com_example_jni_JNITest.h"

//中文问题
JNIEXPORT jstring JNICALL Java_com_example_jni_JNITest_chineseChars
(JNIEnv *env, jobject jobj, jstring in) {
    char *c_str = (*env)->GetStringUTFChars(env, in, NULL);
    printf("%s\n", c_str);
    char *c_str_out = "传出一段文字";
    //NewStringUTF 是utf-16编码
    jstring j_str = (*env)->NewStringUTF(env, c_str_out);
    //使用Java处理乱码问题
    jclass jcls = (*env)->FindClass(env, "java/lang/String");
    //jmethodID
    jmethodID c_mid = (*env)->GetMethodID(env, jcls, "<init>", "([BLjava/lang/String;)V");
    //byte[]数组
    //jbyteArray -> char[]
    jbyteArray bytes = (*env)->NewByteArray(env, strlen(c_str_out));
    //byte数组赋值
    (*env)->SetByteArrayRegion(env, bytes, 0, strlen(c_str_out), c_str_out);
    //字符编码
    jstring charsetName = (*env)->NewStringUTF(env, "GB2312");
    //调用构造函数,返回编码之后的字符串

    return (*env)->NewObject(env, jcls, c_mid, bytes, charsetName);
}

  • 将c代码生成解决文件(动态库.dll)
  • 将动态库文件拷贝到java项目根路径下
  • 运行JNITest.java ,即可看到输出结果。

四、JNI数组处理,传入一个数组,返回另一个数组

  • 编写JNITest.java文件
public class JNITest {
    
    public native int[] sortArray(int[] array);

    public static void main(String[] args){
        JNITest t = new JNITest();
        int[] arr = new int[]{6,8,1,17,23,12,65,32};
        int newArr = t.sortArray();
        for (int i : arr) {
            System.out.println(i);
        }
        System.out.println("返回的新数组为:");
         for (int i : newArr) {
            System.out.println(i);
        }
    }

    
    static {
        System.loadLibrary("JNIProject");
    }
}
  • 在com_example_jni_JNITest.h文件声明对应的方法;签名可通过下面的命令获取
javap -s -p com.example.jni.JNITest

产生的结果中有对应的属性和方法的签名

/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"
/* Header for class com_example_jni_JNITest */

#ifndef _Included_com_example_jni_JNITest
#define _Included_com_example_jni_JNITest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_jni_JNITest
 * Method:    sortArray
 * Signature: 
 */
JNIEXPORT jintArray JNICALL Java_com_example_jni_JNITest_sortArray
(JNIEnv *, jobject,jintArray);
#ifdef __cplusplus
}
#endif
#endif

  • 在JNITest.c文件实现com_example_jni_JNITest.h中声明的方法
#include "com_example_jni_JNITest.h"
#include <stdlib.h>

int compare(int *a,int *b){
    return (*a) - (*b);
}

//数组处理
JNIEXPORT jintArray JNICALL Java_com_example_jni_JNITest_sortArray
(JNIEnv *env, jobject jobj, jintArray arr) {
    //拿到 jintArray 的指针 -> 得到 c int 数组
    jint *elems = (*env)->GetIntArrayElements(env,arr,NULL);
    //数组的长度
    int len = (*env)->GetArrayLength(env,arr);
    
    //排序
    qsort(elems, len, sizeof(jint), compare);
    //同步,第4个参数:
    //JNI_OK(0):Java数组进行更新,并且释放C/C++数组
    //JNI_COMMIT(1):Java数组进行更新,不释放C/C++数组(函数执行完数组会释放)
    //JNI_ABOUT:Java数组不进行更新,但释放C/C++数组
    (*env)->ReleaseIntArrayElements(env, arr, elems,JNI_COMMIT);
    
    
    //将原数组倒序生成一个新的数组
    jintArray jint_new_arr = (*env)->NewIntArray(env,len);
    jint *new_elems = (*env)->GetIntArrayElements(env,jint_new_arr,NULL);
    int i = len-1;
    for(; i >= 0; i--){
        int j = len-1-i;
        new_elems[j] = elems[i];
    }
    //同步
    (*env)->ReleaseIntArrayElements(env, jint_new_arr, new_elems, JNI_COMMIT);
    return jint_new_arr;
}

  • 将c代码生成解决文件(动态库.dll)
  • 将动态库文件拷贝到java项目根路径下
  • 运行JNITest.java ,即可看到输出结果。
    原文作者:onestravel
    原文地址: https://www.jianshu.com/p/d62635fd66cf
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞