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程序间接调用目标原生函数。
自己实现一个 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】即可获取。
参考资料
如何编译 cpp
Visual Studio Code 如何编写运行 C、C++ 程序?
jdk 安装
https://www.jianshu.com/p/cec45e1c5b1c
JNI提示找不到依赖的库文件,java.lang.UnsatisfiedLinkError。
https://blog.csdn.net/missingu1314/article/details/12650725/