# Java 基础 - Java native方法以及JNI实践

\[TOC]

> 转载：[Java native方法以及JNI实践](https://www.jianshu.com/p/1ba925157f7d)

## 1. 前言

今天看AndFix实现时，核心方法之ReplaceMethod方法是一个native方法，之前并没有遇到过，所以在此整理记录。

### 1.1 native的作用

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

1. native 是用做java 和其他语言（如c++）进行协作时使用的，也就是native 后的函数的实现不是用java写的。
2. 既然都不是java，那就别管它的源代码了，我们只需要知道这个方法已经被实现即可。
3. native的意思就是通知操作系统， 这个函数你必须给我实现，因为我要使用。 所以native关键字的函数都是操作系统实现的， java只能调用。
4. java是跨平台的语言，既然是跨了平台，所付出的代价就是牺牲一些对底层的控制，而java要实现对底层的控制，就需要一些其他语言的帮助，这个就是native的作用了。

## 2. JNI简介

native方法是通过java中的JNI实现的。JNI是Java Native Interface的 缩写。从Java 1.1开始，Java Native Interface (JNI)标准成为java平台的一部分，它允许Java代码和其他语言写的代码进行交互。

JNI一开始是为了本地已编译语言，尤其是C和C++而设计 的，但是它并不妨碍你使用其他语言，只要调用约定受支持就可以了。

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

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

![2020-07-06-yPvDfb](https://image.ldbmcs.com/2020-07-06-yPvDfb.jpg)

windows，基于native的PE结构，windows的jvm基于native结构，Java的应用体系构建于jvm之上。jvm通过加载此jni程序间接调用目标原生函数。

![2020-07-06-pI5u1u](https://image.ldbmcs.com/2020-07-06-pI5u1u.jpg)

### 2.1 JNI的生成步骤——Mac版

1. 编写带有native声明的方法的java类，生成.java文件
2. 使用javac命令编译所编写的java类，生成.class文件
3. 使用javah -jni java类名生成扩展名为h的头文件，也即生成.h文件
4. 使用C/C++（或者其他编程想语言）实现本地方法，创建.h文件的实现，也就是创建.cpp文件实现.h文件中的方法
5. 将C/C++编写的文件生成动态连接库，生成jnilib文件

## 3. JNI实例

接下来我们按照上述步骤一个一个来生成JNI实例。

### 3.1 编写带有native声明的java类，HelloWorld.java

```java
public class HelloWorld {
    public native void sayHelloWorld(); //申明一个native方法

    static{
        System.loadLibrary("HelloWorldImpl"); //装入动态链接库,"HelloWorldImpl"是装入动态链接库的名称
    }

    public static void main(String[] args){
        HelloWorld helloWorld = new HelloWorld();
        helloWorld.sayHelloWorld();
    }
}
```

### 3.2 使用javac生成HelloWorld.class

```java
javac HelloWorld.java
```

### 3.3 使用javah -jni java类生成扩展名为h的头文件

```java
javah -jni HelloWorld
```

**此处需要注意的是，我们应该在包名的根目录下执行，因为生成的文件的文件名需要引用包目录。不然就会报找不到类文件的错误**。

生成com\_think\_jni\_HelloWorld.h文件：

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

#ifndef _Included_com_think_jni_HelloWorld
#define _Included_com_think_jni_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_think_jni_HelloWorld
 * Method:    displayHelloWorld
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_think_jni_HelloWorld_displayHelloWorld
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

```

这里我们可以这样理解：这个h文件相当于我们在java里面的接口，这里声明了一个 Java\_HelloWorld\_displayHelloWorld (JNIEnv \*, jobject);方法，然后在我们的本地方法里面实现这个方法，也就是说我们在编写C/C++程序的时候所使用的方法名必须和这里的一致。

### 3.4 使用C/C++实现本地方法

创建HelloWorldImpl.cpp，代码如下所示：

```c
#include "jni.h"
#include "com_think_jni_HelloWorld.h"
#include <stdio.h>
JNIEXPORT void JNICALL Java_com_think_jni_HelloWorld_displayHelloWorld(JNIEnv *env,jobject obj){
    printf("Hello World!\n");
    return;
}
```

### 3.5 将本地方法编写的文件生成动态链接库

```c
gcc -I/Library/Java/JavaVirtualMachines/[根据装的jdk版本来定]/Contents/Home/include/ -dynamiclib HelloWorldImpl.cpp -o libhell.jnilib
```

但是会出现运行这个命令报错：

![2020-07-06-ddKseT](https://image.ldbmcs.com/2020-07-06-ddKseT.jpg)

我们找到这个文件在：

```shell
/Library/Java/JavaVirtualMachines/[根据安装的jdk版本来定]/Contents/Home/include/darwin/
```

这个目录下，所以我们再链接该目录到以上命令中：

```bash
gcc -I/Library/Java/JavaVirtualMachines/jdk1.8.0_71.jdk/Contents/Home/include/ -I/Library/Java/JavaVirtualMachines/jdk1.8.0_71

.jdk/Contents/Home/include/darwin/ -dynamiclib HelloWorldImpl.cpp -o libhell.jnilib
```

最终成功生成libhell.jnilib。

### 3.6 运行可执行文件

把上述文件放在同一个文件夹后，执行：

```java
java HelloWorld
```

即可看到：

![2020-07-06-xUtQ3T](https://image.ldbmcs.com/2020-07-06-xUtQ3T.jpg)
