0%

Java-JNI-Demo

阅读更多

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
2
3
4
5
6
7
8
9
10
11
12
13
package org.liuyehcf.jni;

/**
* Created by HCF on 2018/1/13.
*/
public class JniDemo {
public static void main(String[] args) {
System.loadLibrary("Hello");
sayHello();
}

private static native void sayHello();
}
  1. 加载本地Library,名字为"Hello",这是我们后面将会创建的动态链接库文件
  2. 调用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)

命令如下(任选一种方式)

  1. javah -classpath <.java或.class的路径都可以> -d <输出目录> org.liuyehcf.jni.JniDemo
  2. javah org.liuyehcf.jni.JniDemo:默认类加载路径是当前文件夹的路径,输出目录默认当前文件夹

生成的头文件org_liuyehcf_jni_JniDemo.h如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class org_liuyehcf_jni_JniDemo */

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

#ifdef __cplusplus
}
#endif
#endif

比较重要的一点是#include <jni.h>这一句,编译器会从环境变量指定的路径中去查找jni.h,因此在编译时需要指定jni.h的路径,后面在生成动态库的时候会详细说明,这里先提一下

2.3 编写cpp文件

实现很简单,不废话,直接上代码

1
2
3
4
5
6
7
#include <iostream>
#include "org_liuyehcf_jni_JniDemo.h"

JNIEXPORT void JNICALL Java_org_liuyehcf_jni_JniDemo_sayHello
(JNIEnv *, jclass){
std::cout<<"hello, This is JNI method!"<<std::endl;
}

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文件所在的目录中

  1. 修改org_liuyehcf_jni_JniDemo.h文件,将第一句#include <jni.h>改成#include "jni.h"
  2. 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所在目录>

4 参考