现在的位置: 首页 > 综合 > 正文

JNI简单示例

2018年05月23日 ⁄ 综合 ⁄ 共 5021字 ⁄ 字号 评论关闭

    JNI(Java Native Interface)是Java本地方法调用接口,从Java1.1开始,Java Native Interface(JNI)标准就成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。

    Java中很多地方都使用到了JNI,如System.arrayCopy方法:

  1. public static native void arraycopy(Object array1, int start1, Object array2, int start2, int length);  

    那Java中为什么要使用JNI呢?这与Java产生的历史就有一定的关系了,Java刚开始被设计的时候就是为了跨平台,Java的字节码由JVM运行。Java跨平台是非常好的设计理念,这避免了因为换平台而不得不重新写一套代码的麻烦,可正是为了实现这样的特性,Java也失去了一些特性,如对操作系统底层的调用限制。JNI可以实现对操作系统底层的调用,JNI可以用来提高调用的速度,如我们上面提到的System.arrayCopy方法,它是直接和操作系统的内存进行交互,而省去了JVM和操作系统进行内存交换的步骤;JNI的另一个使用场景就是某些核心类库的实现可能需要跨包调用或者需要绕过其他Java安全性检查,如Java中的sun.misc.Unsaef实现。

    下面我们用两个实例来说明,如何创建自己的DLL,以及如何通过Java去调用这些DLL。

    示例一、不传参数的简单调用

    这个示例很简单,没有输入参数也没有输出参数,只是通过Java调用JNI方法,并把JNI中输出的一句话给显示出来。如果是涉及到输入输出参数的本地方法调用,相对就会麻烦一点,因为Java中的参数和DLL中的参数类型是不一样的,这时就需要一个中间转换间,如SWIG(Simplified Wrapper and Interface Generator)简单包装及接口生成器,这个后面会有介绍。

    1、Java的操作步骤

    1)、首先准备一个具有本地方法的Java文件JNITest,输入内容如下:

  1. package test;  
  2. public class JNITest {  
  3.     /* 
  4.      * 建立一个无返回参数的方法,该方法只在DLL方法内打印一条语句。  
  5.      */  
  6.     public native void test();  
  7. }  

    2)、使用Javac编译该Java文件

  1. javac test/JNITest.java  

    注:编译一定要在test包外操作,否则会报错。

    3)、使用Javah生成头文件

  1. javah test.JNITest  

    注:也是要在test包外操作
    操作完成后,我们可以看到头文件"test_JNITest.h",它的命令方式是以包名加类名的方式,其内容如下:

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

    正如文件头部所说,不要对这个生成文件进行任何的修改一样,然后通过C实现该方法即可:

  1. JNIEXPORT jint JNICALL Java_test_JNITest_test (JNIEnv *, jobject);  

   然后我们将实现后的C,编译成DLL,再将DLL放到操作系统的PATH中,如我是WINDOWS中,将其放到system32路径下即可,DLL的名称没有关系。

    4)、执行测试结果

    建立一个测试类JNITestCaller

  1. public class JNITestCaller {  
  2.     static {  
  3.         // 在系统路径中(如system32)加载名为JNITest.dll文件  
  4.         System.loadLibrary("JNITest");  
  5.     }  
  6.   
  7.     public static void main(String[] arg) {  
  8.         JNITest jniTest = new JNITest();  
  9.         jniTest.test();  
  10.     }  
  11. }  

    通过javac编译该Java类

  1. javac test/JNITestCaller  

    然后执行它

  1. java test.JNITestCaller  

    如果控制台打印出“=====888”字符串,则表示通过JNI调用DLL成功且执行成功,如果不成功则检查步骤是否有遗漏。

    2、DLL编译的步骤

    1)、安装VC6, VS太大了,我等也不常用,这个可以了;

    2)、启动VC6,新建DLL工程,通过“File->New...->Dll工程”,名称就命名为JNITest吧,到时编译过后的DLL名称就是JNITest.dll,如果是其它的名称,在编译成DLL后需要改一下名称;

    3)、新建C++

    选中新建工程JNITest,然后点击菜单中的“File->New...->C++ Source File”,文件的名称可以任意,在其中输入如下代码:

在其中输入如下如下代码:

  1. #include <jni.h>  
  2. #include <stdlib.h>  
  3. #include <test_JNITest.h>  
  4. #ifdef __cplusplus  
  5. extern "C" {  
  6. #endif  
  7. /*这个方法名称一定要和头文件的一模一样,不过头文件中的参数只有类型,没有名称,需要加成如下参数*/  
  8. JNIEXPORT void JNICALL Java_test_JNITest_test(JNIEnv *jenv, jobject jobj) {  
  9.   /*就打印这一条语句*/  
  10.   printf("=====888");  
  11. }  
  12. #ifdef __cplusplus  
  13. }  
  14. #endif  

    4)、将%JAVA_HOME%/include/win32下面的jawt_md.h、jni_mh.h,以及%JAVA_HOME%/include/jni.h,这三个文件拷贝到%VS_HOME%/VC98/Include下面,生成的头文件test_JNITest.h也可以拷贝到那个目录,省去指定头文件的路径步骤;

    5)、点击C++的编译按钮(或者CTRL+F7),不报错则通过,则点击生成DLL文件的按钮(或者F7),到%VS_HOME%/MyProject/JNITest目录,将JNITest.dll文件拷贝到system32目录就可以了。

 

    示例二、有输入输出参数的调用,需要借助工具SWIG(Simplified Wrapper and Interface Generator)

    Java中JNI的操作,最难的就是Java与Dll之间参数的传递与转换了,因为JAVA中的参数与C中的参数是不能够直接匹配的,如果我们手工去处理,还真的有点麻烦。这个时候我们可以通过SWIG这个工具来做参数的包装,它可以帮忙做传入传出参数之间的转换,我们现在上面的基础之上,将原来的void且不传参数的方法,改成传入两个字符串,返回的结果为这两个字符串的组合,如传入参数为"A"、"B",则输出结果为"AB",操作步骤如下:

    1、Java中的操作

    Java中需要将Native方法增加两个输入参数以及修改返回参数,这个Native方法的功能就是返回两个输入参数的拼接结果,如参数输入“A”和“B”,那要得到的结果是“AB”,修改后的包含Native方法的类的代码如下:

  1. package test;  
  2. public class JNITest {  
  3.     /* 
  4.      * 建立一个无返回参数的方法,该方法只在DLL方法内打印一条语句。 另外就是传参的处理要相对复杂点,后面介绍。 
  5.      */  
  6.     public native String test(String a, String b);  
  7. }  

    此时我们的测试代码也会有一点点小小的改变,增加传入参数以及获取返回结果,修改后的JNITestCaller代码如下:

  1. package test;  
  2. public class JNITestCaller {  
  3.     static {  
  4.         // 在系统路径中(如system32)加载名为JNITest.dll文件  
  5.         System.loadLibrary("JNITest");  
  6.     }  
  7.   
  8.     public static void main(String[] arg) {  
  9.         JNITest jniTest = new JNITest();  
  10.         String a = "A", b = "B";  
  11.         String result = jniTest.test(a, b);  
  12.         System.out.println("The execute result is:" + result);  
  13.     }  
  14. }  

    如果我们按照上面的操作,并可以在最后得到输出结果“AB”,那么就表示我们的操作成功了。

    2、DLL编译的步骤

    1)、首先是下载SWIG,要包括WIN的那个版本,这个版本才有SWIG.EXE这个文件;

    2)、然后设置SWIG当前路径到PATH中,因为执行SWIG命令的时候需要用到;

    3)、编写".i"文件如“I.i”,这个文件里面定义要生成的C方法是什么,这个方法就是要实现的方法,具体怎么写就需要看看SWIG的示例与帮助文档了,里面还是挺丰富的;

    4)、执行命令“swig -java -c++ I.i”,会生成C语言包装、C++包装、两个JAVA文件,其中包括了声明本地native方法类,以及引用方法native方法的类,但是在实现类中没有实现MAIN方法以及通过System.loadLibary的方法加载DLL,这两个需要用户自己完成。

    注:用户需要自己通过C或者是C++实现在".i"文件中声明的方法,才可以成功编译,因为C及C++的包装文件中会引用这个方法,我们不需要修改包装文件,直接在另外一个C或者是C++文件中实现那个文件即可。

    5)、调用和上面的一样,编译JAVA,执行JAVA,不过不用调用JAVAH生成H头文件了,因为由SWIG已经生成了对应的包装文件。

    这个没有给出示例,因为SWIG生成的包装文件比较大,它其中做了字符的转换,有了上面的基础,再通过SWIG去实现,应该没有多大难度。

    注:DLL的步骤,后面会补充完整。

抱歉!评论已关闭.