【詳解】JNI(Java Native Interface)(一)

貓毛·波拿巴發表於2018-10-22

前言:

  一提到JNI,多數程式設計者會下意識地感受到一種無法言喻的恐懼。它給人的第一感覺就是"難",因為它不是單純地在JVM環境內操作Java程式碼,而是跳出虛擬機器與其他程式語言進行互動。

  你可能至今還沒聽說過這個技術,但是如果你是一個原始碼愛好者,或者有翻閱過JDK的一些原始碼,那你一定有接觸過native方法。你是否因為查閱原始碼直到native方法戛然而止,但又由於它的空方法體,而對底層原理不知所以? 本文就帶讓你瞭解JNI。並通過一些案例來自己實現JNI的互動。

什麼是JNI?

  JNI 全稱 Java Native Interface。Java本地方法介面,它是Java語言允許Java程式碼與C、C++程式碼互動的標準機制。維基百科是這樣解釋的:“當應用無法完全用Java程式語言實現的時候,(例如,標準Java類庫不支援的特定平臺特性或者程式庫時),JNI使得程式設計者能夠編寫native方法來處理這種情況”。這就意味著,在一個Java應用程式中,我們可以使用我們需要的C++類庫,並且直接與Java程式碼互動,而且在可以被呼叫的C++程式內,反過來呼叫Java方法(回撥函式)。

 

JNI的優點

  (1)JNI使得一些"過程"無需在Java中實現。例如,硬體敏感的,或者直接與作業系統API關聯的命令。

  (2)由於使用底層的庫,如圖形,計算,各種型別的渲染等等,可以提高應用的執行效能。

  (3)已經有大量的庫已經被實現,程式設計者可直接使用,不用再自行編寫。這裡的庫指的是用其他程式語言實現的程式庫,例如IO流或者執行緒等底層與OS互動的操作都是由C/C++實現的。

 

具體實現原理

  互動模式如圖

  

 

要從Java呼叫C++函式,你需要進行以下操作: 

  1. 在Java類中建立一個native方法,此方法被本類其他方法呼叫

        

  2. 建立一個標頭檔案,可以利用javah命令生成。

      在標頭檔案中定義它的簽名,如下所示:

  

  介面規範:

  JNIEXPORT <返回型別> JNICALL Java_<包名>_<類名>_<方法名>(JNIEnv*, <原物件引用>,<引數1>..<引數n>)

  • extern "C" 只被C++編譯器識別,標明此方法利用C的函式命名協議來編譯。
  • JNIEXPORT 是JNI必要的修飾符。
  • 資料型別帶有"j"字首的:jdouble,jobject..等是Java物件或型別在C++中的對映
  • JNIEnv* 指向JNI 環境,可以利用其呼叫所有JNI函式
  • jobject 引用當前Java物件

  3. 建立一個原始檔,實現標頭檔案中定義的介面。實現內容就是Java程式碼呼叫的C/C++程式碼。

  4. 編譯標頭檔案和原始檔生成C/C++動態連結庫 .so/.dll 檔案

  5. 此native方法所在類,載入動態連結庫。因為載入連結庫要在執行native方法之前,所以此載入過程一般放在靜態初始化塊內執行。

   

  或

  

  總結一下,從Java程式碼中呼叫C/C++程式碼的流程

  (1)建立一個有native標識的方法,並且從其他Java方法呼叫它

  (2)Java編譯器生成位元組碼

  (3)C/C++ 編譯器生成動態庫  .so檔案(Linux)或 .dll檔案(Windows)

  (4)執行程式,執行位元組碼

  (5)執行到loadLibary或load呼叫的時候,新增一個 .so檔案到這個程式中

  (6)執行到native方法的時候,通過方法簽名,在已開啟的.so檔案中進行搜尋。

  (7)如果連結庫內有對應方法,就會被執行,否則程式崩潰

  注:由於windows沒找到生成動態連結庫的工具,又不想安裝C/C++開發環境,故以下案例都在以CentOS為作業系統的虛擬機器內執行

  案例一:從Java呼叫C程式碼輸出Hello World   

  此案例所有生成的所有檔案如下:

  

  (1)建立JNI資料夾,建立Java檔案如下:

  

  這裡,我們定義了一個native方法,是個空方法體,我們在主函式內對其進行呼叫。

  注:這裡使用的是System.load從絕對路徑引用動態連結庫,當然也可以使用loadLibrary方法,其是從java.library.path對應路徑下搜尋對應名稱的庫檔案並載入。

  (2)編譯HelloJNI.java檔案,生成類檔案

  

  (3)利用JDK提供的JNI命令工具,javah生成 .h標頭檔案。

  

  注:發現Linux環境下,javah居然不能從當前資料夾掃描到類檔案,需要指定類路徑 其中 -cp 就是-classpath

  以下是利用javah生成的標頭檔案。

  

  (4)建立HelloJNI.c檔案,編寫實現體

  

  (5)利用gcc生成動態連結庫,注意我們這裡有引用到jni.h這個標頭檔案,此檔案由JDK提供,另外jni.h還引用了jni_md.h這個檔案。必須引入這兩個標頭檔案,才能通過編譯。

  兩個檔案的所在地,本人JDK的安裝路徑在/usr/java下,每個人可能都不一樣。

  

  

  在gcc命令內通過指定( -I 路徑 )引入庫所在的目錄,利用前面前面的標頭檔案和原始檔編譯成動態連結庫 hello.so

  

  

  (6)執行java程式

  

  由圖可知,我們成功呼叫了C的程式碼

相關文章