天天看點

java 進階用法之:無所不能的 java, 本地方法調用實況

作者:默默de念你
java 進階用法之:無所不能的 java, 本地方法調用實況

簡介

相信每個程式員都有一個成為 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++的項目:

java 進階用法之:無所不能的 java, 本地方法調用實況

注意,這個項目的 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 方法。

java 進階用法之:無所不能的 java, 本地方法調用實況

繼續閱讀