簡介
相信每個程式設計師都有一個成為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方法呢?有的,這就是JNA,我們會在後續的文章中深入進行介紹。
本文的程式碼可以參考https://github.com/ddean2009/learn-java-base-9-to-20.git
本文已收錄於 http://www.flydean.com/01-jni-overview/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!