使用JNI来输出Hello JNI

272 阅读5分钟

简介

萌新自学过程,内容很基础,甚至有错误,望各位大佬不要嘲笑理解万岁


JNI具体是什么我就不说了,Java本地接口,调用库用的,库可以用C/C++什么的来编写。今天我们就想调用接口来输出个Hello JNI。来看一下步骤。

Java中的步骤

Java中的步骤比较简单,只需要新建一个类,写一个带有native标记的函数,加载库,最后写个主函数调用native函数。

public class nativeTest {
    public static native void sayHello();   //带有native声明的函数表示这个函数要用C/C++实现

    static {
        System.loadLibrary("helloNative");  //生成的dll文件名称
    }

    public static void main(String[] args) {
        sayHello();                         //调用sayHello()方法
    }
}

加载dll文件

加载dll文件有两种方式。System.load()和System.loadLibrary()。这两个有什么区别呢?表面上看,可能就是load()是绝对路径,loadLibrary()是相对路径啊。我们这里只需要知道上述说明就可以了。

生成.h头文件

第一步,我们需要用javah命令生成.h文件

javah nativeTest.java

会生成同名nativeTest.h文件。我们打开看看

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class nativeTest */

#ifndef _Included_nativeTest
#define _Included_nativeTest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     nativeTest
 * Method:    sayHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_nativeTest_sayHello
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

我们发现只需要实现

JNIEXPORT void JNICALL Java_nativeTest_sayHello
  (JNIEnv *, jclass);

即可。

编写.c文件

那么,我们就新建nativeTest.c文件,并且引入nativeTest.h

#include"nativeTest.h"      //注意这块因为在同目录引用要用引号引入
#include<stdio.h>

JNIEXPORT void JNICALL Java_nativeTest_sayHello(JNIEnv * jni, jclass jclazz){
	printf("hello,jni");    //实现方法
}

保存文件。

使用gcc生成.dll动态链接库

这块就有坑了。 打开命令行,我们使用如下命令

gcc -shared -o helloNative.dll nativeTest.c

我们可能会遇到下面的错误

命令行提示我们,没有jni.h文件。确实,jin.h在jdk中,gcc正常肯定找不到。所以我们应该去jdk目录中的include目录中和include/win32目录中分别把jni.h、jawt_md.h、jni_md.h三个文件复制到gcc的include文件夹下。

然后我们在执行上面的命令

我们看到目录下已经生成了helloNative.dll文件。到这里,我们应该可以愉快的运行java程序了吧!我们来看一下。

运行java程序

不行!出现了下面的错误

Exception in thread "main" java.lang.UnsatisfiedLinkError: no helloNative in java.library.path: [E:\JetBrains\IntelliJ IDEA 2019.3.1\jbr\bin, C:\WINDOWS\Sun\Java\bin, C:\WINDOWS\system32, C:\WINDOWS, C:\Program Files (x86)\Common Files\Intel\Shared Libraries\redist\intel64_win\compiler, C:\Program Files (x86)\Intel\iCLS Client\, C:\Program Files\Intel\iCLS Client\, C:\Windows\system32, C:\Windows, C:\Windows\System32\Wbem, C:\Windows\System32\WindowsPowerShell\v1.0\, C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common, C:\Program Files (x86)\Intel\Intel(R) Management Engine Components\DAL, C:\Program Files\Intel\Intel(R) Management Engine Components\DAL, C:\Program Files (x86)\Intel\Intel(R) Management Engine Components\IPT, C:\Program Files\Intel\Intel(R) Management Engine Components\IPT, C:\WINDOWS\system32, C:\WINDOWS, C:\WINDOWS\System32\Wbem, C:\WINDOWS\System32\WindowsPowerShell\v1.0\, C:\WINDOWS\System32\OpenSSH\, C:\Program Files\NVIDIA Corporation\NVIDIA NvDLISR, C:\WINDOWS\system32, C:\WINDOWS, C:\WINDOWS\System32\Wbem, C:\WINDOWS\System32\WindowsPowerShell\v1.0\, C:\WINDOWS\System32\OpenSSH\, E:\Git\cmd, D:\daima\Android\sdk\platform-tools, D:\daima\Android\sdk\tools, E:\Java\jdk8\bin, E:\Java\jdk8\jre\bin, E:\Python\Python37, D:\MATLAB\R2017a\runtime\win64, D:\MATLAB\R2017a\bin, C:\Program Files\dotnet\, D:\mysql\bin, E:\mingw64\bin, D:\daima\Android\sdk\platform-tools, E:\Microsoft VS Code\bin, ., .]
	at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2660)
	at java.base/java.lang.Runtime.loadLibrary0(Runtime.java:829)
	at java.base/java.lang.System.loadLibrary(System.java:1870)
	at nativeTest.<clinit>(nativeTest.java:5)

错误是说没有helloNative在java.library.path中。那么这些path都有哪些呢?我们可以用以下代码来查看

System.getProperty("java.library.path");

经过查看,我们发现这些是包括在系统属性->环境变量->系统变量->path中设置的值

那么我们就可以在这里添加一下我们存放dll的目录。但是添加完后我们发现还是上面的报错 ==(这我就不知道原因了,各位小伙伴也注意似乎没有人说java.library.path就是系统变量的path,我瞎说的,上面这段就不要信了)。==

其实,我们可以在IDEA的编辑配置选项中增加虚拟机配置

添加以下字段

-Djava.library.path=D:\daima\Java\test\src

其中 -Djava.library.path= 是固定写法,后面的目录是dll文件的真实路径。

这回该配置的也配置了,应该可以运行了吧,我们再尝试一下:

Exception in thread "main" java.lang.UnsatisfiedLinkError: D:\daima\Java\test\src\helloNative.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform
	at java.base/java.lang.ClassLoader$NativeLibrary.load0(Native Method)
	at java.base/java.lang.ClassLoader$NativeLibrary.load(ClassLoader.java:2430)
	at java.base/java.lang.ClassLoader$NativeLibrary.loadLibrary(ClassLoader.java:2487)
	at java.base/java.lang.ClassLoader.loadLibrary0(ClassLoader.java:2684)
	at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2649)
	at java.base/java.lang.Runtime.loadLibrary0(Runtime.java:829)
	at java.base/java.lang.System.loadLibrary(System.java:1870)
	at nativeTest.<clinit>(nativeTest.java:5)

又报错了!我们来看一下这回是怎么回事。它说不能在64位系统上加载32位的库。也难怪,0202年了,大部分人的计算机都是64位了吧,不能运行也情有可原。那这回应该怎么办呢?

这回是gcc的锅,或者说是MinGW的锅。我们一般正常安装的MinGW都是32位的,这里我们应该选择64位的MinGW64。官网下载太慢了,大家可以选择国内下载地址。解压好后,我们用这个gcc来生成dll文件。然后测试一下:

芜湖,经过不懈努力,我们的hello jni终于出现在了控制台。

总结

总结一下步骤

  • 编写java类,内容包括native标记的函数,还有要调用加载库文件的方法。
  • 使用javah命令生成.h头文件
  • 编写.c文件,不要忘记引入.h头文件
  • 使用gcc来生成.dll文件,注意位数

中间我觉得对初学者的坑就是jni.h文件的复制啊,虚拟机配置语句啊,mingw位数选择啊等等。总之,折腾了一段时间,总算是亲自体验了一把jni,这波不亏~