基于IntelliJ IDEA的JNI入门案例

基于IntelliJ IDEA的JNI入门案例

参考链接

前置知识

JNI

JNI是Java Native Interface的缩写,通过使用 Java本地接口书写程序,可以确保代码在不同的平台上方便移植。 [1] 从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他编程语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的。例如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少要保证本地代码能工作在任何Java 虚拟机环境。

​ ----百度百科

众所周知java是一个跨平台的语言,用java编译的代码可以运行在任何安装了jvm的系统上。然而各个系统的底层实现肯定是有区别的,为了使java可以跨平台,于是jvm提供了叫java native interface(JNI)的机制。当java需要使用到一些系统方法时,由jvm帮我们去调用系统底层,而java本身只需要告知jvm需要做的事情,即调用某个native方法即可。

例如,当我们需要启动一个线程时,无论在哪个平台上,我们调用的都是start0方法,由jvm根据不同的操作系统,去调用相应系统底层方法,帮我们真正地启动一个线程。因此这就像是jvm为我们提供了一个可以操作系统底层方法的接口,即JNI,java本地接口。

.dll文件

​ DLL(Dynamic Link Library)文件为动态链接库文件,又称“应用程序拓展”,是软件文件类型。在Windows中,许多应用程序并不是一个完整的可执行文件,它们被分割成一些相对独立的动态链接库,即DLL文件,放置于系统中。当我们执行某一个程序时,相应的DLL文件就会被调用。一个应用程序可使用多个DLL文件,一个DLL文件也可能被不同的应用程序使用,这样的DLL文件被称为共享DLL文件。

​ DLL文件中存放的是各类程序的函数(子过程)实现过程,当程序需要调用函数时需要先载入DLL,然后取得函数的地址,最后进行调用。使用DLL文件的好处是程序不需要在运行之初加载所有代码,只有在程序需要某个函数的时候才从DLL中取出。另外,使用DLL文件还可以减小程序的体积。

需要注意的是,在windows中,动态链接库后缀为dll,而在linux中,其后缀为so。

gcc

​ GCC是一个用于linux系统下编程的编译器,是一个用于编程开发的自由编译器。最初,GCC只是一个C语言编译器,它是GNU C Compiler的英文缩写。随着众多自由开发者的加入和GCC自身的发展,如今的GCC已经是一个包含众多语言的编译器了。其中包括C,C++,Ada,Object C和Java等。所以,GCC也由原来的GNU C Compiler变为GNU Compiler Collection。也就是GNU编译器家族的意思。当然,如今的GCC借助于它的特性,具有了交叉编译器的功能,即在一个平台下编译另一个平台的代码。

Windows系统如何生成dll文件

目前我所知的方法分为两种:

  1. 借助vs或vc生成
  2. 通过windows下的gcc编译器生成
    • Min GW
    • CygWin
    • ......

以下介绍一下Min GW。

Min GW

MinGW,是Minimalist GNUfor Windows的缩写。根据上文gcc的定义,这里Min GW的定义就很明显了,就是Windows下的一款编译器。

安装Min GW

关于dll的生成,在下一节的Demo中讲解。

JNI Demo:JniTest

  1. 新建JniTest.java

    package cyt.demo.jni;
    
    public class JniTest {
    
        static {
            //加载动态库
            System.loadLibrary("JniTest");
        }
    
        public native void jniHello();
    
        public static void main(String[] args) {
            JniTest jniTest = new JniTest();
            jniTest.jniHello();
        }
    }
    
    • 后续我将以JniTest作为dll的名称,所以在静态代码块中,我加载了JniTest。这里加载时只需填写文件名,无需加入后缀。因为Java跨平台的特性,这段代码在任何安装了jvm的系统上都应该能够使用。若这里加载JniTest.dll,首先System.loadLibrary在这样的输入下会检索不到指定库文件,其次就算这样的输入被允许,当当前程序被放到windows以外的系统执行时,执行会失败,因为只有在windows中动态链接库的后缀才为dll。

      image-20210124162529644

    • 使用native关键字声明方法jniHello为本地方法。

  2. 编译JniTest,并生成native方法的头文件

    javac -h . JniTest.java
    
    • 注意-h后面有一个.,意思是生成的头文件,存放在当前目录

    • 命令也可拆分为javac JniTest.javajavah JniTest

    • 最后得到的头文件cyt_demo_jni_JniTest.h如下图:

      image-20210124163320564

    • 接下来我们只需实现Java_cyt_demo_jni_JniTest_jniHello(JNIEnv *, jobject)即可。仔细观察就会发现这个函数名称是有规律的,即Java_<包>_<类名>_<函数名>

    • 关于JNI的类型映射,可参考此文 IntelliJ IDEA平台下JNI编程(二)—类型映射

    • JNIEXPORT 和 JNICALL 都是宏定义,用于指定JNI函数和本地方法实现之间的调用和链接规则。必须将JNIEXPORT放在函数的返回类型之前,将JNICALL放在函数名称与返回类型之间。

  3. 编写c文件实现jniHello

    #include <jni.h>
    #include <stdio.h>
    #include "cyt_demo_jni_JniTest.h"
    
    JNIEXPORT void JNICALL Java_cyt_demo_jni_JniTest_jniHello(JNIEnv *env, jobject thisObj){
        printf("Hello,JNI!");
        return;
    }
    
    
    • 需要注意的是,在头文件中,系统只为该方法的参数定义了数据类型,并未指定变量名,这里需要自己指定。
  4. 使用Min GW生成dll

    gcc -c -I "D:\Java\jdk1.8\include" -I "D:\Java\jdk1.8\include\win32" cyt_demo_jni_JniTest.c
    
    • -I,表示include,如果不加这两个-I,在编译时会找不到jni.hjni_md.h
    • 编译完成,获得cyt_demo_jni_JniTest.o
  5. 将cyt_demo_jni_JniTest.o转化为JniTest.dll

    gcc -Wl,--add-stdcall-alias -shared -o JniTest.dll cyt_demo_jni_JniTest.o
    
  6. 得到JniTest.dll

    image-20210124165119385

  7. 运行JniTest.main(),查看输出结果

    image-20210124165217895

    • 这里会出现找不到JniTest.dll的情况,原因在于我并未把dll放在jvm默认的加载库的位置。此时需要为当前程序设置虚拟机参数。

      Run→edit configurations→VM options

      image-20210124165634339

posted @ 2021-02-28 15:06  snail-coder  阅读(1266)  评论(0)    收藏  举报