![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIjBXPt9mcm9TNmNGMjFmY3QTY2ADN0ADO1QTY0ATYiBzMzkjZ5IDN28CXwsWO0EHbyomdx1Sat42YtM3b09CXul2ZpJ3bvwVbvNmLn1WavFWa0V3b05iNyA3Lc9CX6MHc0RHaiojIsJye.jpg)
簡介
相信每個程式員都有一個成為 C++大師的夢想,畢竟 C++程式員處于程式員鄙視鍊的頂端,他可以俯視任何其他語言的程式員。
但事實情況是,無數的程式員從小白到放棄,鑒于 C++的難度,最後都投入了 java 的懷抱。JAVA 以他寬廣的胸懷接納了一衆無法登頂 C++的程式員。
開個玩笑,C 和 C++的優勢在于和系統底層的互動和其運作的速度和效率,JAVA 的優勢在與廣泛的應用架構,可以快速搭建所需的應用程式。兩者各有所長。
架構的好處就是降低了程式開發的難度,讓應用程式可以快速批量複制。
大家知道,JVM 底層是使用 C 和 C++來編寫的,而 JAVA 位元組碼适合 JVM 進行互動的,是以直覺上看來,JAVA 是可以和底層的 C++代碼進行互動的。那麼如何互動呢?會不會很複雜?
今天本文帶大家一一揭曉。
JDK 的本地方法
所謂本地方法就是調用作業系統或者其他底層庫的方法。這些方法屬于系統的外部接口,用于程式和作業系統之間進行互動。大家想一下,JDK 中有哪些本地的方法呢?
第一個想到的應該就是檔案操作,因為檔案操作肯定需要依賴與系統底層提供的 IO 接口。我們先具體來看一下 File 的 delete 方法的實作:
public boolean delete() {
@SuppressWarnings("removal")
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkDelete(path);
}
if (isInvalid()) {
return false;
}
return fs.delete(this);
}
File 的 delete 方法首先調用 SecurityManager 來進行權限判斷,看是否可以删除。如果可以删除則繼續調用 FileSystem 的 delete 方法。
我們繼續檢視 FileSystem 的 delete 方法:
public abstract boolean delete(File f);
可以看到 FileSystem 中的 delete 方法是一個抽象方法,需要具體的實作。
而這個實作是和平台有關系的,如果你是 linux 或者 mac 系統,那麼它的實作類是 UnixFileSystem,它的 delete 方法如下:
public boolean delete(File f) {
if (useCanonCaches) {
cache.clear();
}
if (useCanonPrefixCache) {
javaHomePrefixCache.clear();
}
return delete0(f);
}
private native boolean delete0(File f);
可以看到,delete 方法最終會調用 delete0 方法,而這個方法是一個 native 方法,表示該方法需要調用系統本地的方法。
JDK 提供了一個 JAVA 調用本地系統方法的實作,叫做 JNI,全稱是 Java Native Interface,它是從 JAVA1.1 中引入的一項技術。它允許 Java 代碼和其他語言寫的代碼進行互動。
為了驗證 JNI 的可行性,我們接下來自己實作一個 native 的方法,并在 java 中調用,看看是否能夠成功。
自定義 native 方法
在 JAVA 中定義 native 方法很簡單,我們隻需要在方法描述前面加上 native 關鍵字即可,這個方法并不需要任何實作。舉個具體的例子如下:
public class JNIUsage {
public native void printMsg();
public static void main(String[] args) {
//加載C檔案
System.loadLibrary("JNIUsage");
JNIUsage jniUsage = new JNIUsage();
jniUsage.printMsg();
}
}
上面的例子中,我們定義了一個 native 的 printMsg,然後在 main 中首先加載包含該實作的 Library 檔案,之後就可以像正常的 JAVA 方法一樣進行調用。
那麼這麼實作這個 native 方法呢?
不管熟悉還是不熟悉 C++的朋友應該都聽過頭檔案的概念,一般來說我們在頭檔案中定義好要實作的方法,然後在具體的内容檔案中對頭檔案中定義的方法進行實作。
是以頭檔案中需要包含這個 printMsg 的方法,生成頭檔案可以使用 javah 指令。
首先進入到 JNIUsage 源檔案的根目錄,運作下面的指令:
javah -classpath . -jni com.flydean.JNIUsage
該指令會在項目源代碼的根目錄中生成一個 com_flydean_JNIUsage.h 檔案。打開看看,具體的内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_flydean_JNIUsage */
#ifndef _Included_com_flydean_JNIUsage
#define _Included_com_flydean_JNIUsage
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_flydean_JNIUsage
* Method: printMsg
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_flydean_JNIUsage_printMsg
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
簡單點講,該 head 檔案中定義了一個需要實作的 Java_com_flydean_JNIUsage_printMsg 方法。
接下來,我們需要對這個頭檔案進行實作。
這裡我們使用 JetBrain 公司的 Clion 開發工具,首先建立一個 c++的項目:
注意,這個項目的 type 需要是 shared 類型。
然後将 com_flydean_JNIUsage.h 檔案拷貝到項目的根目錄下。
這時候是編譯不了的,你會發現很多依賴包的錯誤,我們還需要将 JDK home 目錄中 include 目錄下的 jni.h 檔案,和 jni_md.h 檔案(如果是 windows 平台該檔案在 win32 目錄下,如果是 mac 平台,該檔案在 darwin 目錄下),拷貝到項目的根目錄下。
這樣編譯的錯誤就不見了。
最後我們修改預設的 library.cpp 檔案,引入 com_flydean_JNIUsage.h 并實作其中的方法如下所示:
#include "com_flydean_JNIUsage.h"
#include <iostream>
JNIEXPORT void JNICALL Java_com_flydean_JNIUsage_printMsg
(JNIEnv *, jobject){
printf("this is www.flydean.com!");
}
目前為止,項目的代碼結構應該如下圖所示:
<img src="https://img-blog.csdnimg.cn/ee0512f8cab94d47ae8a13fd6a062e92.png" style="zoom:50%"/>
接着 build-->Build 'JNIUsage', 生成 libJNIUsage.dylib 檔案:
====================[ Build | JNIUsage | Debug ]================================
/Applications/CLion.app/Contents/bin/cmake/mac/bin/cmake --build /Users/flydean/data/git/cplus/JNIUsage/cmake-build-debug --target JNIUsage
[2/2] Linking CXX shared library libJNIUsage.dylib
Build finished
有了 libJNIUsage.dylib,我們還需要将其加入 JAVA 項目中的 path 中:
<img src="https://img-blog.csdnimg.cn/f5b938e8a753482d86fc792e870b8fe7.png" style="zoom:50%"/>
選擇 java-jni 的 module,在依賴中選擇 JARs or Directories, 選擇剛剛的 libJNIUsage.dylib 目錄。
儲存之後,就可以運作 JAVA 代碼了,結果如下:
/Library/Java/JavaVirtualMachines/jdk-17.0.1.jdk/Contents/Home/bin/java -Djava.library.path=/Users/flydean/data/git/cplus/JNIUsage/cmake-build-debug -Dfile.encoding=UTF-8 -classpath /Users/flydean/data/git/learn-java-base-9-to-20/java-jni/target/classes: com.flydean.JNIUsage
this is www.flydean.com!
或者你可以在指令行中将 libJNIUsage.dylib 加入到 java 運作的 classpath 中即可。
總結
以上就是一個簡單的使用 JAVA 調用 native 方法的例子。大家可以看到,步驟還是挺複雜的,那麼有沒有其他更加簡單的方法,讓 JAVA 來調用 native 方法。