调用JNI函数

写在最前面

在上一篇博客中,讲解了JNI的运行方式以及Java中调用C函数的步骤,本篇博客将讲解在由C语言编写的JNI本地函数中如何控制Java端的代码,主要包括以下内容

创建Java对象

访问类静态成员域

调用类的静态方法

访问Java对象的成员变量

访问Java对象的方法

调用JNI函数的示例程序

在开始之前,我们先大致看一下示例程序的整体架构。整个示例程序由JniFuncMain类(包含本地方法声明)、JniTest对象、libjnifunc.jnilib(包含本地方法的具体实现)三部分组成。


整个示例程序从调用JniFuncMain类的createJniObject()本地方法开始,该方法经由JNI与libjnitest.jnilib中命名为Java_JniFuncMain_createJniObject()的C函数链接在一起。

Java层代码(JniFuncMain.java)

1. JniFuncMain类

  1 public class JniFuncMain
  2 {
  3     private static int staticIntField = 300;
  4 
  5     // 加载本地库
  6     static { System.loadLibrary("jnifunc");}
  7 
  8     // 本地方法声明
  9     public static native JniTest createJniObject();
 10 
 11     public static void main(String[] args)
 12     {
 13         // 从本地代码生成JniTest对象
 14         System.out.println("[Java] createJniObject()调用本地方法");
 15         JniTest jniObj = createJniObject();
 16 
 17        // 调用JniTest对象的方法
 18        jniObj.callTest();
 19     }
 20 }

2.JniTest类

  1 class JniTest
  2 {
  3     private int intField;
  4 
  5     // 构造方法
  6     public JniTest(int num)
  7     {
  8         intField = num;
  9         System.out.println("[java] 调用JniTest对象的构造方法:intField = " + intField);
 10     }
 11 
 12     // 此方法由JNI本地函数调用
 13     public int callByNative(int num)
 14     {
 15         System.out.println("[Java] JniTest对象的callByNative(" + num + ")调用");
 16         return num;
 17     }
 18     
 19     public void callTest()
 20     {
 21         System.out.println("[Java] JniTest对象的callTest()方法调用:intField = " + intField);
 22     }
 23 } 

分析JNI本地函数代码

1.JniFuncMain.h头文件

在JniFuncMain.java代码中,仅仅在本地方法createJniObject()进行了声明,接下来,我们用C语言实现它。首先,使用javah命令,生成本地方法的函数原型,命令如下(此命令之前要先编译JniFuncMain.java文件)。

javac JniFuncMain.java
javah JniFuncMain
执行以上命令后,生成如下JniFuncMain.h头文件

1 /* DO NOT EDIT THIS FILE - it is machine generated */
  2 #include <jni.h>
  3 /* Header for class JniFuncMain */
  4 
  5 #ifndef _Included_JniFuncMain
  6 #define _Included_JniFuncMain
  7 #ifdef __cplusplus
  8 extern "C" {
  9 #endif
 10 /*
 11  * Class:     JniFuncMain
 12  * Method:    createJniObject
 13  * Signature: ()LJniTest;
 14  */
 15 JNIEXPORT jobject JNICALL Java_JniFuncMain_createJniObject
 16   (JNIEnv *, jclass);
 17 
 18 #ifdef __cplusplus
 19 }
 20 #endif
 21 #endif

仔细查看上面的代码,可以看到createJniObject()本地方法对应的JNI本地函数原型,形式如下

JNIEXPORT jobject JNICALL Java_JniFuncMain_createJniObject (JNIEnv *, jclass);
这个函数原型与我们的Java类JniFuncMain的createJniObject()本地方法相对应。在上一篇博客中,我们知道使用javah命令生成的JNI本地函数原型的第二个参数默认类型为jobject。但观察上面的代码,会发现它的第二个参数类型为jclass,而不是jobject。原因是什么?若想弄清楚这个问题,首先要理解函数原型第二个参数的含义。在上一篇博客中,我们理解到,jobject类型的变量用来保存调用本地方法的对象的引用。

那么我们再回过头看看我们本地方法的声明

public static native JniTest createJniObject();
这个本地方法被声明为static(静态)方法。在Java中,调用静态方法时,可以不用创建对象,所以通过所在的类直接调用即可,即本地静态方法通过类而非对象进行调用,因此函数原型的第二个参数类型为jclass类型。

2.JniFuncMain.c文件

  1 #include "JniFuncMain.h"
  2 #include <stdio.h>
  3 
  4 JNIEXPORT jobject JNICALL Java_JniFuncMain_createJniObject(JNIEnv *env, jclass clazz)
  5 {
  6     jclass targetClass;
  7     jmethodID mid;
  8     jobject newObject;
  9     jstring helloStr;
 10     jfieldID fid;
 11     jint staticIntField;
 12     jint result;
 13 
 14     // 获取JniFuncMain类的staticIntField变量值
 15     fid = (*env)->GetStaticFieldID(env,clazz,"staticIntField","I");
 16     staticIntField = (*env)->GetStaticIntField(env,clazz,fid);
 17     printf("[CPP]获取JniFuncMain类的staticIntField值\n");
 18     printf("       JniFuncMain.staticIntField = %d\n", staticIntField);
 19 
 20     // 查找生成对象的类
 21     targetClass = (*env)->FindClass(env,"JniTest");
 22 
 23     // 查找构造方法
 24     mid = (*env)->GetMethodID(env,targetClass,"<init>","(I)V");
 25 
 26     // 生成JniTest对象(返回对象的引用)
 27     printf("[CPP] JniTest对象生成\n");
 28     newObject = (*env)->NewObject(env,targetClass,mid,100);
 29 
 30     // 调用对象的方法
 31     mid = (*env)->GetMethodID(env,targetClass, "callByNative","(I)I");
 32     result = (*env)->CallIntMethod(env,newObject,mid,200);
 33 
 34     // 设置JniObject对象的intField值
 35     fid = (*env)->GetFieldID(env,targetClass,"intField","I");
 36     printf("[CPP]设置JniTest对象的intField值为200\n");
 37     (*env)->SetIntField(env,newObject,fid,result);
 38 
 39     // 返回对象的引用
 40     return newObject;
 41 }

编译

执行如下命令

gcc -dynamiclib -I /Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/include/ JniFuncMain.c -o libjnifunc.jnilib
得到C链接库,

然后执行java JniFuncMain,得到如下结果

[Java] createJniObject()调用本地方法
[CPP]获取JniFuncMain类的staticIntField值
       JniFuncMain.staticIntField = 300
[CPP] JniTest对象生成
[java] 调用JniTest对象的构造方法:intField = 100
[Java] JniTest对象的callByNative(200)调用
[CPP]设置JniTest对象的intField值为200
[Java] JniTest对象的callTest()方法调用:intField = 200

达到我们的预期。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值