关于JNI你应该知道的一切

出于效率的问题,很多情况下,我们需要在上层的Java代码中调用底层 C或C++实现,这时jni就可以大显身手了。jni(Java Native Interface)允许Java代码和其他语言写的代码进行交互,使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样 做是可以接受的,甚至是必须的。例如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。

使用JNI编程的步骤

关于javah:在命令窗口中运行javah -h 命令我们可以看到javah的所有选项,这里简单介绍一下:

编写第一个JNI程序

看到了上面的步骤我们来亲自实践一下,利用jni编写一个简单的hello world程序。 1.首先,我们用Java代码编写一个本地方法hello:

package com.example.jnitest;{();}

2.接下来我们利用javah生成头文件,需要注意的是我们应该首先编译该工程得到.class文件。然后我们运行命令行来到工程目录/bin目录 下,输入命令 javah -classpath . com.example.jnitest.JniTest 得到头文件,如图:

得到头文件的代码如下:

extern “C” {#endif/* * Class:com_example_jnitest_JniTest * Method: hello * Signature: ()V */JNIEXPORT void JNICALL Java_com_example_jnitest_JniTest_hello (JNIEnv *, jclass);#ifdef __cplusplus}#endif#endif

3.接下来我们要用C语言实现我们的程序,为了方便创建共享库文件,在这里我在VS2012中创建了一个控制台工程,并在应用程序设置中选择dll和空项目。如图:

接着将刚刚得到的头文件添加进来并新建一个cpp文件来实现我们的程序。

#include”jni_hello.h”JNIEXPORT void JNICALL Java_com_example_jnitest_JniTest_hello(JNIEnv *, jclass){printf(“Hello Jni!\n”);}

注意问题,如果我们直接将头文件添加进来可能会找不到头文件jni.h,这时我们需要配置一下我们的工程,右键 工程–>属性–>VC++目录,选择包含目录–>编辑,将jni头文件的目录添加进来,一般需要添加的目录是jdk目录下的 include文件夹以及include/win32文件夹,如图:

4.生成共享库文件,接下来我们只需要运行该工程即可在Debug文件夹下找到.dll文件,需要注意的是,vs直接生成的是32位的dll文件,如果你 的机器是64位就会报错Can’t load IA 32-bit .dll on a AMD 64-bit platform,这时我们就用配置一下自己的VS工程:生成–>配置管理器的活动解决方案平台,活动解决方案平台–>新建–>选择 x64即可

接下来我们需要将我们的共享库添加进来:

首先我们调用System.out.println( System.getProperty(“java.library.path”));获得共享库的路径接下来我们将dll文件拷贝到其中之一的路径中

5.加载共享库,调用native方法:

package com.example.jnitest;public class Main {static{System.loadLibrary(“JniApplication”);//静态初始化块加载库}(String[] args) {JniTest.hello();}}

在控制台中输出结果:

Jni中数据类型对应关系

了解了jni的简单用法,我们再来进行深入的学习:首先使jni中Java基本类型和C基本类型的对应关系

除了基本的数据类型外,比较常用的当属字符串类型,Java中的String对应C中为jstring,我们可以使用如下方法将jstring类型和char *的相互转化

char *ff = “Hello Jni!”;//创建一个jstring类型字符串jstring j_string = env->NewStringUTF(ff);* c_string = env->GetStringUTFChars(j_string, 0);printf(c_string);//当我们不再使用字符串时,要将其释放env->ReleaseStringUTFChars(j_string, c_string);访问Java中的域

使用C语言访问Java中的域和方法都要知道其Id,使用起来相对也比较复杂,在这里举一个简单的例子,假设我们的类People中有一个int类型的域age,即:

package com.example.jnitest;public class People {private int age;() {return age;}(int age) {this.age = age;}}

修改JniTest中的方法,这一次我们通过调用native方法使得age域变为原来的二倍:

package com.example.jnitest;{People changeAge(People people);}

重新生成头文件后再cpp文件中编写我们的方法,每次访问Java中的域我们需要得到FieldId才可以,其步骤为: 1. 获取隐式参数的类 2. 获取域Id 3. 访问域的值

#include”jni_hello.h”JNIEXPORT j (JNIEnv *env, j){j;jfieldID c_ageId = env->GetFieldID(cl_people, “age”, “I”);jint c_age = env->GetIntField(ob,c_ageId);//打印修改之后的语句printf(“%d”, c_age);//修改age域env->SetIntField(ob, c_ageId, 2*c_age);return ob;}

env->GetFieldID方法中的第二个参数表示该域的名称,第三个参数表示该age域的类型为int,这个jni的编码签名有关,稍后会提到。接下来在main方法中调用:

package com.example.jnitest;public class Main {static{System.loadLibrary(“JniApplication”);}(String[] args) {People people = new People();people.setAge(5);people = JniTest.changeAge(people);System.out.println(“changed::”+people.getAge());}}逆境磨练人、逆境是老师、逆境之苦可变甜。

关于JNI你应该知道的一切

相关文章:

你感兴趣的文章:

标签云: