通常為了更加靈活高效地實現計算邏輯,我們一般使用C/C++實現,編譯為動態庫,併為其設定C介面和C++介面。用C++實現的一個庫其實是一個或多個類的簡單編譯連結產物。然後暴露其實現類構造方法和純虛介面類。這樣就可以通過多型呼叫到庫內部的實現類及其成員方法。進一步地,為了讓不同庫之間呼叫相容,可以將C++介面進一步封裝為一組C介面函數,C介面編譯時不會新增複雜的函數簽名,也不支援函數過載,可以方便其他C或C++客戶程式呼叫。C介面的封裝需要有"extern C{}"標識,以告訴編譯器請使用C編譯方式編譯這些函數。
進一步地,為了方便上層應用呼叫C/C++庫, 如Android應用,可以為C++庫封裝Java介面。jdk中地jni元件可以方便地實現在java中呼叫c++庫函數。基本呼叫原理如下:
這裡假定C++類別庫已經預編譯好了,有現成的so庫和c介面使用。首先明確一點就是,我們要為C++庫封裝一個java介面,也即在java層使用C++庫暴露的所有函數,那麼:
system.loadLibrary("libname.so");
載入第三步編譯生成的jni so庫,即可間接呼叫到c++庫函數。PS:
jni層型別和java型別的對應關係,基本資料型別只是簡單地加了字首j
,如int<=>jint, double<=>jdouble
,下面是一些物件型別(包含陣列)的型別對映關係:
簽名規則對應表
String 字串函數操作
// 在jni實現函數中把jstring型別的字串轉換為C風格的字串,會額外申請記憶體
const char *str = env->GetStringUTFChars(string,0);
// 做檢查判斷
if (str == NULL){
return NULL;
}
// do something;
// 使用完之後釋放申請的記憶體
env->ReleaseStringUTFChars(string,str);
JNI 支援將 jstring 轉換成 UTF 編碼和 Unicode 編碼兩種。因為 Java 預設使用 Unicode 編碼,而 C/C++ 預設使用 UTF 編碼。所以使用GetStringUTFChars(jstring string, jboolean* isCopy)
將 jstring 轉換成 UTF 編碼的字串。其中,jstring 型別引數就是我們需要轉換的字串,而 isCopy 引數的值在實際開發中,直接填 0或NULL就好了,表示深拷貝。
當呼叫完GetStringUTFChars
方法時別忘了做完全檢查。因為 JVM 需要為產生的新字串分配記憶體空間,如果分配失敗就會返回 NULL,並且會丟擲 OutOfMemoryError 異常,所以要對 GetStringUTFChars 結果進行判斷。
當使用完 UTF 編碼的字串時,還不能忘了釋放所申請的記憶體空間。呼叫 ReleaseStringUTFChars
方法進行釋放。
除了將 jstring 轉換為 C 風格字串,JNI 還提供了將 C 風格字串轉換為 jstring 型別。
通過 NewStringUTF
函數可以將 UTF 編碼的 C 風格字串轉換為 jstring 型別,通過NewString
函數可以將 Unicode 編碼的 C 風格字串轉換為 jstring 型別。這個 jstring 型別會自動轉換成 Java 支援的 Unicode 編碼格式。
除了 jstring 和 C 風格字串的相互轉換之外,JNI 還提供了其他的函數。
參考:https://blog.csdn.net/TLuffy/article/details/123994246
實踐出真知,分別建立一個c++工程和java工程,原始碼github地址。
結構目錄如下:
├── cpp_project
│ ├── build.sh
│ ├── CMakeLists.txt
│ ├── include
│ │ ├── c_api.h
│ │ ├── com_Student.h
│ │ └── student.h
│ ├── jni_impl
│ │ └── jni_impl.cpp
│ ├── src
│ │ ├── c_api.cpp
│ │ └── student.cpp
│ └── test
│ └── main.cpp
└── java_project
├── com
│ ├── Student.java
│ └── Test.java
├── com_Student.h
└── run.sh
整體構建流程如下:
javac -encoding utf8 -h ./ com/Student.java
命令生成naive本地介面.h標頭檔案。將其拷貝到c++工程下。 // 載入jni庫
static {
try {
System.loadLibrary("jnilib");
}
catch(UnsatisfiedLinkError e) {
System.err.println(">>> Can not load library: " + e.toString());
}
}
# 編譯java檔案
javac -encoding utf8 com/Test.java -d bin
# 執行java檔案
java -Djava.library.path=/root/project/lzq/jni_demo/cpp_project/build/bin -cp bin com.Test
PS: 編譯指令碼分別在cpp工程和java工程目錄下