java native 关键字

使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了DLL,由java去调用。

这些函数的实现体在DLL中,JDK的源代码中并不包含,你应该是看不到的。对于不同的平台它们也是不同的。

这也是java的底层机制,实际上java就是在不同的平台上调用不同的native方法实现对操作系统的访问的。

作用

native是与C++联合开发的时候用的!使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了DLL,由java去调用。

native 是用做java 和其他语言(如c++)进行协作时使用的,也就是native 后的函数的实现不是用java写的。

既然都不是java,那就别管它的源代码了,我们只需要知道这个方法已经被实现即可。

native的意思就是通知操作系统, 这个函数你必须给我实现,因为我要使用。 所以native关键字的函数都是操作系统实现的, java只能调用。

java是跨平台的语言,既然是跨了平台,所付出的代价就是牺牲一些对底层的控制,而java要实现对底层的控制,就需要一些其他语言的帮助,这个就是native的作用了

JNI

JNI (Java Native Interface,Java本地接口)是一种编程框架,使得Java虚拟机中的Java程序可以调用本地应用/或库,也可以被其他程序调用。

本地程序一般是用其它语言(C、C++或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序。

目的和作用

有些事情Java无法处理时,JNI允许程序员用其他编程语言来解决,例如,Java标准库不支持的平台相关功能或者程序库。也用于改造已存在的用其它语言写的程序,供Java程序调用。许多基于JNI的标准库提供了很多功能给程序员使用,例如文件I/O、音频相关的功能。当然,也有各种高性能的程序,以及平台相关的API实现,允许所有Java应用程序安全并且平台独立地使用这些功能。

JNI框架允许Native方法调用Java对象,就像Java程序访问Native对象一样方便。Native方法可以创建Java对象,读取这些对象,并调用Java对象执行某些方法。当然Native方法也可以读取由Java程序自身创建的对象,并调用这些对象的方法。

dll 交互技术

目前java与dll交互的技术主要有3种:jni,jawin和jacob。

目前功能性而言:jni » jawin > jacob,其大致的结构如下图:

输入图片说明

windows,基于native的PE结构,windows的jvm基于native结构,Java的应用体系构建于jvm之上。

jvm通过加载此jni程序间接调用目标原生函数。

windows

自己实现一个 native 方法

(1)Java程序中声明native修饰的方法,类似于abstract修饰的方法,只有方法签名,没有方法实现。编译该java文件,会产生一个.class文件。

(2)使用javah编译上一步产生的class文件,会产生一个.h文件。

(3)写一个.cpp文件实现上一步中.h文件中的方法。

(4)将上一步的.cpp文件编译成动态链接库文件.dll。

(5)最后就可以使用System或是Runtime中的loadLibrary()方法加载上一步的产生的动态连接库文件了。

编写 Java 类

/**
 * @author binbin.hou
 * @since 1.0.0
 */
public class Hello {

    public native void h();

    static{
        System.load("D:\\code\\cpp\\hello\\bin\\Release\\hello.dll");
        //System.loadLibrary("hello");
    }

    public static void main(String[] args){
        new Hello().h();
    }

}

System.load("D:\\code\\cpp\\hello\\bin\\Release\\hello.dll"); 通过绝对路径指定需要加载的 dll,这样方便一点。

当然也可以通过 System.loadLibrary("hello");,然后系统会通过 System.getProperty("java.libary.path") 对应的路径寻找 dll 文件。

  • 执行编译

可以使用命令:javac Hello.java 编译为 Hello.class 文件。

javah 编译

我们使用下面的命令编译 class 文件

javah -jni Hello

注意:这里如果是命令行打开,需要保证文件需要在当前命令行的根目录(当然目录)

不然可能会报错,我一开始就是一直报错:

javah -jni Hello 错误: 找不到 'Hello' 的类文件。

生成文件

执行之后会生成一个 Hello.h 文件。

这个学过 C 预言的小伙伴应该不陌生,其实就是头文件,内容如下:

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

#ifndef _Included_Hello
#define _Included_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     Hello
 * Method:    h
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_Hello_h
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

这个头文件引入了 jni.h,我们自定义的 h() 方法也变成了

JNIEXPORT void JNICALL Java_Hello_h
  (JNIEnv *, jobject);

编写 hello.cpp

因为我们在第一步 System.loadLibrary("hello"); 中调用的文件名称为hello,所以这里的.cpp文件必须为 hello.cpp 文件。

  • hello.cpp

hello.cpp : 定义 DLL 应用程序的导出函数。

#include "Hello.h"

JNIEXPORT void JNICALL Java_Hello_h(JNIEnv *, jobject) {
    printf("Hello! ");
}

为了避免找不到文件,我们把下面的 4 个文件要放在同一个文件夹下。

Hello.class  hello.cpp  Hello.h  Hello.java

如何编译 dll codeblocks 尝试

环境 windows7

编辑器

采用比较轻量开源的 code blocks

mingw 编译器

下载

下载编译器:MinGW-w64 - for 32 and 64 bit Windows 往下稍微翻一下,选最新版本中的x86_64-posix-seh。

最好不要用 Download Latest Version,这个是在线安装包,可能因为国内的“网络环境”下载失败。

我下载的是:x86_64-8.1.0-release-posix-seh-rt_v6-rev0.7z

这是一个 7z 压缩文件,下载之后,解压。

解压

解压之后,可能会有多余的文件夹,注意找到 bin 目录。

bin/            etc/      lib/      licenses/  share/
build-info.txt  include/  libexec/  opt/       x86_64-w64-mingw32/

为了方便,我放在 D 盘,bin 路径如下:

D:\tool\mingw64\bin

为了使用方便,我们将这个 bin 加到 PATH 下。

和以前配置 jdk 之类的一个套路。

配置 PATH

  • 图形化模式

找到环境变量:右键“此电脑”,选属性;或者按win+PauseBreak。

选左边的高级系统设置,高级,环境变量。

选上面那几个条目中的Path,编辑,新建。然后把含有目标exe的文件夹路径填进去。

例如gcc在D:\tool\mingw64\bin\gcc,就填D:\tool\mingw64\bin,Win大小写不敏感。

  • 命令行模式

命令行的方式:打开cmd或者PS,setx /m path “%path%;C:\mingw\bin"。

此命令无需管理员权限,且不会随着终端退出而退出(就是和上面图形化的效果一样)。

setx /m path "%path%;D:\tool\mingw64\bin"
  • 验证配置
λ gcc -v
Using built-in specs.
...
Thread model: posix
gcc version 8.1.0 (x86_64-posix-seh-rev0, Built by MinGW-W64 project)

编译 cpp

Hello.class  hello.cpp  hello.h  Hello.java  jni.h  jni_md.h

header 依赖

jni.h jni_md.h 这两个是依赖的 Header 文件,可以在 jdk 安装路径下找到。jdk 的 include 目录下。

hello.h 调整

将默认生成的 #include <jni.h> 调整为 #include "jni.h"

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

#ifndef _Included_Hello
#define _Included_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     Hello
 * Method:    h
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_Hello_h
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

code blocks

创建一个 File->New->Project->Dynamic Link Library

删除默认的文件,加入我们的 hello.h hello.cpp

生成的时候要选择Release,不选Debug,这样jdk才能调用,执行编译即可:

g++.exe -Wall -DBUILD_DLL -O2  -c D:\code\cpp\hello\hello.cpp -o obj\Release\hello.o
g++.exe -shared -Wl,--output-def=bin\Release\libhello.def -Wl,--out-implib=bin\Release\libhello.a -Wl,--dll  obj\Release\hello.o  -o bin\Release\hello.dll -s  -luser32
Output file is bin\Release\hello.dll with size 14.50 KB
Process terminated with status 0 (0 minute(s), 0 second(s))
0 error(s), 0 warning(s) (0 minute(s), 0 second(s))

编译之后会在 bin\Release\hello.dll 生成 dll 文件。

位数问题

执行

java Hello
Exception in thread "main" java.lang.UnsatisfiedLinkError: D:\code\cpp\hello\bin\Release\hello.dll: Can't load AMD 64-bit .dll on a IA 32-bit platform
        at java.lang.ClassLoader$NativeLibrary.load(Native Method)
        at java.lang.ClassLoader.loadLibrary0(Unknown Source)
        at java.lang.ClassLoader.loadLibrary(Unknown Source)
        at java.lang.Runtime.load0(Unknown Source)
        at java.lang.System.load(Unknown Source)
        at Hello.<clinit>(Hello.java:6)

64位的jdk要调用64位的dll,32的jdk要调用32位的dll

为什么生成的是一个 64-bit.dll,估计我的 jdk 是 32 位的导致的。

  • jdk 版本查看
 java -version
java version "1.8.0_102"
Java(TM) SE Runtime Environment (build 1.8.0_102-b14)
Java HotSpot(TM) Client VM (build 25.102-b14, mixed mode)

没说明是 64,jdk 就是 32 位的。

我这里使用的是 MinGW-W64 编译的,是一个 64 位的 dll 文件。

解决位数问题

有两种方案,第一生成一个 32 位的 dll,第二换一个 64 位的 jdk。

得,还是选择装一个 64 位的 jdk 吧。

jdk 下载太慢,可以使用华为的镜像:https://repo.huaweicloud.com/java/jdk/

参考 windows jdk 配置,此处不做展开。

java -version
java version "1.8.0_192"
Java(TM) SE Runtime Environment (build 1.8.0_192-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.192-b12, mixed mode)

重新测试

重新编译 class 文件

java Hello
Exception in thread "main" java.lang.UnsatisfiedLinkError: Hello.h()V
        at Hello.h(Native Method)
        at Hello.main(Hello.java:10)

依然报错。

我从到到尾全部重来了一遍,最后终于成功了:

 java Hello
Hello!

小结

一个小小的 native 关键字,想实现一个最简单的 hello world 却踩了很多坑。

主要还是太久没写 c,已经基本忘干净了。好记性不如烂笔头啊。

文本主要介绍了 native 关键字的作用,并且从零到1采坑实现了自己的 hello world。

code blocks 主要是因为个人觉得比较轻量,code blocks+MinGW 也完全可以使用 vs 替代。

这里最坑的估计就是 jdk 的版本了,用到的各个资源下载有时候也比较麻烦。

为了便于其他同学模拟测试,我将几个测试的基本文件资源做了分享,关注【老马啸西风】,后台回复【native】即可获取。

参考资料

Java 本地接口

java native 关键字

详解native方法的使用

如何编译 cpp

VS Code 搭建 C/C++ 编译运行环境的四种方案

Visual Studio Code 如何编写运行 C、C++ 程序?

Codeblocks上dll的创建和使用

jdk 安装

https://www.jianshu.com/p/cec45e1c5b1c

JDK下载过慢的问题解决方案

JNI提示找不到依赖的库文件,java.lang.UnsatisfiedLinkError。

https://blog.csdn.net/missingu1314/article/details/12650725/