调用JNI函数

来源:互联网 发布:淘宝客服提成2个点 编辑:程序博客网 时间:2024/06/06 04:30

写在最前面

在上一篇博客中,讲解了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.javajavah 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