阅读更多
1 JNI简介
JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他编程语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的。例如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少要保证本地代码能工作在任何Java 虚拟机环境
2 Demo详细步骤
以下操作基于macOS,不同的平台下,一些头文件的路径,以及生成动态库的方式不同,请注意区分
2.1 创建Java文件
1 | package org.liuyehcf.jni; |
- 加载本地Library,名字为"Hello",这是我们后面将会创建的动态链接库文件
- 调用native方法
2.2 利用命令行工具javah创建标准.h文件
javah用法
javah [options] <classes>
- 其中,
[options]
包括:-o <file>
:输出文件 (只能使用 -d 或 -o 之一)-d <dir>
: 输出目录-v -verbose
:启用详细输出-h --help -?
:输出此消息-version
:输出版本信息-jni
:生成 JNI 样式的标头文件 (默认值)-force
:始终写入输出文件-classpath <path>
:从中加载类的路径-cp <path>
:从中加载类的路径-bootclasspath <path>
:从中加载引导类的路径
<classes>
是使用其全限定名称指定的
(例如, java.lang.Object)
命令如下(任选一种方式)
javah -classpath <.java或.class的路径都可以> -d <输出目录> org.liuyehcf.jni.JniDemo
javah org.liuyehcf.jni.JniDemo
:默认类加载路径是当前文件夹的路径,输出目录默认当前文件夹
生成的头文件org_liuyehcf_jni_JniDemo.h
如下:
1 | /* DO NOT EDIT THIS FILE - it is machine generated */ |
比较重要的一点是#include <jni.h>
这一句,编译器会从环境变量指定的路径中去查找jni.h,因此在编译时需要指定jni.h的路径,后面在生成动态库的时候会详细说明,这里先提一下
2.3 编写cpp文件
实现很简单,不废话,直接上代码
1 |
|
2.4 编译生成动态库文件
这一步有两种方式
第一种方式:利用-I参数指定依赖头文件的位置
g++ -dynamiclib -I <jni.h文件所在的目录> -I <jni_md.h文件所在的目录> <org_liuyehcf_jni_JniDemo.cpp的路径> -o <动态库的输出目录>/libHello.jnilib
- 在我的电脑上,
jni.h
文件所在目录如下:/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/include
- 在我电脑上,
jni_md.h
文件所在目录如下:/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/include/darwin
第二种方式:修改org_liuyehcf_jni_JniDemo.h
,并且将jni.h
以及jni_md.h
文件拷贝到org_liuyehcf_jni_JniDemo.h
文件所在的目录中
- 修改
org_liuyehcf_jni_JniDemo.h
文件,将第一句#include <jni.h>
改成#include "jni.h"
- 将
jni.h
以及jni_md.h
文件拷贝到org_liuyehcf_jni_JniDemo.h
文件所在的目录中,这两个文件的目录参考上面的说明
g++ -dynamiclib <org_liuyehcf_jni_JniDemo.cpp的路径> -o <动态库的输出目录>/libHello.jnilib
g++参数解释
-dynamiclib
:表示生成动态库,相当于linux环境下的-shared-I
:指定编译的依赖的头文件所在的路径- 注意,如果
org_liuyehcf_jni_JniDemo.cpp
就在当前目录下,那么不需要指定org_liuyehcf_jni_JniDemo.h
的位置,否则需要
- 注意,如果
-o
:指定生成的动态库文件的名称,在macOS环境下,动态库的命名格式为libXXX.jnilib
,相当于linux下的libXXX.so
2.4.1 关于#include <filename>和#include “filename”
In practice, the difference is in the location where the preprocessor searches for the included file.
通常,这两者测差异在于预处理器去哪找这个file
For
#include "filename"
the preprocessor searches in the same directory as the file containing the directive, and then like for#include <filename>
. This method is normally used to include programmer-defined header files.
对于#include "filename"
而言,预处理器在指令所在的目录中搜寻,后续处理与#include <filename>
相同,这种方式通常用于包含自定义的头文件
For
#include <filename>
the preprocessor searches in an implementation dependent manner, normally in search directories pre-designated by the compiler/IDE. This method is normally used to include standard library header files.
对于#include <filename>
而言,预处理器以与实现相关的方式搜索,通常在编译器/ IDE预先指定的搜索目录中搜索,这种方式通常用于包含标准库头文件
3 运行
执行如下命令
java -classpath <org.liuyehcf.jni.JniDemo.class的加载路径> -Djava.library.path=<上面生成的libXXX.jnilib所在目录> org.liuyehcf.jni.JniDemo
- 注意
-Djava.library.path
参数指定的是libXXX.jnilib所在的目录
如果在IDE中运行,记得添加VM参数-Djava.library.path=<上面生成的libXXX.jnilib所在目录>