native到CPU
阿新 • • 發佈:2020-09-19
# Native
* 所謂的native準確的說是藉由虛擬機器實現的JNI介面呼叫的作業系統提供的API
* JNI使得class中的ACC_NATIVE標至的方法能借由JNI類的例項轉換為JNI規範(如全限定名)的c實現方法例項(已經由.lib在虛擬機器初始化時載入或者藉由已經載入的類庫的load方法,用java等語言加入記憶體),該例項會呼叫本地方法棧中的方法(作業系統提供的API)
## .h、.cpp、.lib和.dll
.h標頭檔案和.cpp是編譯時必須的,lib是連結時需要的,dll是執行時需要的。
> .h:宣告函式介面
>
> .cpp:c++語言實現的功能原始碼
>
> .lib :
>
> LIB有兩種,一種是靜態庫,比如C-Runtime庫,這種LIB中有函式的實現程式碼,一般用在靜態連編上,它是將LIB中的程式碼加入目標模組(EXE或者DLL)檔案中,所以連結好了之後,LIB檔案就沒有用了。
>
> 一種LIB是和DLL配合使用的,裡面沒有程式碼,程式碼在DLL中,這種LIB是用在靜態呼叫DLL上的,所以起的作用也是連結作用,連結完成了,LIB也沒用了。至於動態呼叫DLL的話,根本用不上LIB檔案。 目標模組(EXE或者DLL)檔案生成之後,就用不著LIB檔案了。
>
> .dll:
>
> 動態連結庫英文為**DLL,是Dynamic Link Library**的縮寫。DLL是一個包含可由多個程式,同時使用的程式碼和資料的庫。
>
> 當程式使用 DLL 時,具有以下的優點: 使用較少的資源,當多個程式使用同一個函式庫時,DLL 可以減少在磁碟和實體記憶體中載入的程式碼的重複量(執行時需要的庫是需要加入記憶體的)。
.h和.cpp編譯後會生成.lib和.dll 或者 .dll 檔案
我們的程式引用別的檔案的函式,需要呼叫其標頭檔案,但是標頭檔案找到相應的實現有兩種方式,一種是同個專案目錄下的其他cpp檔案(公用性差),一種是連結時的lib檔案(靜態,lib中自己有實現程式碼),一種是執行時的dll檔案,一種是lib和dll 的結合(動態,lib放索引,dll為具體實現)
> 還要指定編譯器連結相應的庫檔案。在IDE環境下,一般是一次指定所有用到的庫檔案,編譯器自己尋找每個模組需要的庫;在命令列編譯環境下,需要指定每個模組呼叫的庫。
一般不開源的系統是後面三種方式,因為可以做到介面開放,原始碼閉合
#### 靜態連結庫
靜態連結庫(Static Libary,以下簡稱“靜態庫”),靜態庫是一個或者多個obj檔案的打包,所以有人乾脆把從obj檔案生成lib的過程稱為Archive,即合併到一起。比如你連結一個靜態庫,如果其中有錯,它會準確的找到是哪個obj有錯,即靜態lib只是殼子,但是靜態庫本身就包含了實際執行程式碼、符號表等等。
如果採用靜態連結庫,在連結的時候會將lib連結到目的碼中,結果便是lib 中的指令都全部被直接包含在最終生成的 EXE 檔案中了。
這個lib檔案是靜態編譯出來的,索引和實現都在其中。
靜態編譯的lib檔案有好處:給使用者安裝時就不需要再掛動態庫了。但也有缺點,就是導致應用程式比較大,而且失去了動態庫的靈活性,在版本升級時,同時要釋出新的應用程式才行。
#### 動態連結庫(DLL)
.dll + .lib : 匯入庫形式,在動態庫的情況下,有兩個檔案,而一個是引入庫(.LIB)檔案,一個是DLL檔案,引入庫檔案包含被DLL匯出的函式的名稱和位置,DLL包含實際的函式和資料,應用程式使用LIB檔案連結到所需要使用的DLL檔案,庫中的函式和資料並不複製到可執行檔案中,因此在應用程式的可執行檔案中,存放的不是被呼叫的函式程式碼,而是DLL中所要呼叫的函式的記憶體地址,這樣當一個或多個應用程式執行是再把程式程式碼和被呼叫的函式程式碼連結起來,從而節省了記憶體資源。
從上面的說明可以看出,DLL和.LIB檔案必須隨應用程式一起發行,否則應用程式將會產生錯誤。
.dll形式: 單獨的可執行檔案形式,因為沒有lib 的靜態載入,需要自己手動載入,LoadLibary調入DLL檔案,然後再手工GetProcAddress獲得對應函數了,若是java 會呼叫System的LoadLibary,但是也是呼叫JVM中對於作業系統的介面,使用作業系統的LoadLibary等方法真正的將.dll讀入記憶體,再呼叫生成的相應函式。
.dll+ .lib和.dll本質上是一樣的,只是前者一般用於通用庫的預設定,是的我們通過lib直接能查詢到.dll檔案,不用我們自己去查詢,雖會消耗一部分效能,但是實用性很大。.dll 每一個需要到的檔案都需自己呼叫載入命令,容易出錯與浪費較多時間(但是我們測試時卻可以很快的看出功能實現情況,而且更靈活地呼叫)
## JNI
JNI是Java Native Interface的縮寫,通過使用 [Java](https://baike.baidu.com/item/Java/85979)本地介面書寫程式,可以確保程式碼在不同的平臺上方便移植,它允許Java程式碼和其他語言寫的程式碼進行互動。
java生成符合JNI規範的C介面檔案(標頭檔案):
1. 編寫帶有native宣告的方法的java類
2. 使用[javac](https://baike.baidu.com/item/javac)命令編譯所編寫的java類
3. 然後使用javah + java類名生成副檔名為h的標頭檔案
4. 使用C/C++實現本地方法
5. 將C/C++編寫的檔案生成動態連線庫 (linux gcc windows 可以用VS)
編寫範例:https://blog.csdn.net/wzgbgz/article/details/82979728
生成的.h的樣例:
```cpp
/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"
/* Header for class NativeDemo */
#ifndef _Included_NativeDemo
#define _Included_NativeDemo
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: NativeDemo
* Method: sayHello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_NativeDemo_sayHello
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
```
> “jni.h” 是必須要匯入的,因為JNIEXPORT等都需要他的支援才行,而且有些方法中需要藉助裡面的函式。
>
> Java_NativeDemo_sayHello這樣的規範命名是生成的.dll在被作業系統dlopen讀取入記憶體時返回的handle能經由dlsym截取出正確的函式名,他可能將xxx.dll全都載入入記憶體,放入一個handle或者一個handle集合中,這時就需要包的全限定類名來確定到底獲取的是handle中的哪個方法了
### **JNIEnv** **,jobject** **,jclass**
**1. JNIEnv**類實際代表了Java環境,通過這個JNIEnv 指標,就可以對Java端的程式碼進行操作。例如,建立Java類的物件,呼叫Java物件的方法,獲取Java物件的屬性等等,JNIEnv的指標會被JNI傳入到本地方法的實現兩數中來對Java端的程式碼進行操作。
JNIEnv類中有很多函式用可以用如下所示其中:TYPE代表屬性或者方法的型別(比如:int float double byte ......)
```cpp
1.NewObject/NewString/NewArray
2.Get/SetField
3.Get/SetStaticField
4.CallMethod/CallStaticMethod等許許多多的函式
```
**2.** **jobject**代表了在java端呼叫本地c/c++程式碼的那個類的一個例項(物件)。在修改和呼叫java端的屬性和方法的時候,用jobject 作為引數,代表了修改了jobject所對應的java端的物件的屬性和方法
**3. jclass** : 為了能夠在c/c++中使用java類,JNI.h標頭檔案中專門定義了jclass型別來表示java中的Class類
JNIEvn中規定可以用以下幾個函式來取得jclass
```
1.jclass FindClass(const char* clsName) ;
2.jclass GetObjectClass(jobject obj);
3.jclass GetSuperClass(jclass obj);
```
## JNI原理
我們編譯xxx.h和xxx.cpp生成了dll檔案,執行java檔案JNI會幫我們呼叫dll中的方法, 但是java物件是如何具體呼叫他的我們不清楚
我們自己實現的dll需要大概如下的模板:
Test.java
```java
package hackooo;
public class Test{
static{
// java層呼叫.dll檔案進入記憶體,但是底層仍是由虛擬機器呼叫JNI用C實現對作業系統的提供的介面載入入記憶體
System.loadLibrary("bridge");
}
public native int nativeAdd(int x,int y);
public int add(int x,int y){
return x+y;
}
public static void main(String[] args){
Test obj = new Test();
System.out.printf("%d\n",obj.nativeAdd(2012,3));
System.out.printf("%d\n",obj.add(2012,3));
}
}
```
我們需要先看到System.loadLibrary("bridge")的作用
```java
@CallerSensitive
public static void loadLibrary(String libname) {
// Runtime類是Application程序的建立後,用來檢視JVM當前狀態和控制JVM行為的類
// Runtime是單例模式,且只能用靜態getRuntime獲取,不能例項化
// 其中load是載入動態連結庫的絕對路徑方法
// loadLibrary是讀取相對路徑的,動態連結庫需要在java.library.path中,一般為系統path,也可以設定啟動項的 -VMoption
// 通過ClassLoader.loadLibrary0(fromClass, filename, true);中的第三個引數判斷
Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);
}
```
java.lang.Runtime
```java
@CallerSensitive
public void loadLibrary(String libname) {
loadLibrary0(Reflection.getCallerClass(), libname);
}
synchronized void loadLibrary0(Class> fromClass, String libname) {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkLink(libname);
}
if (libname.indexOf((int)File.separatorChar) != -1) {
throw new UnsatisfiedLinkError(
"Directory separator should not appear in library name: " + libname);
}
// false,呼叫相對路徑
ClassLoader.loadLibrary(fromClass, libname, false);
}
```
java.lang.ClassLoader
```java
static void loadLibrary(Class> fromClass, String name,
boolean isAbsolute) {
// 通過方法區中的class類找到相應的類載入器
ClassLoader loader =
(fromClass == null) ? null : fromClass.getClassLoader();
if (sys_paths == null) {
// 載入的絕對路徑
// 系統環境變數
usr_paths = initializePath("java.library.path");
// 我們啟動時加入的依賴項
sys_paths = initializePath("sun.boot.library.path");
}
if (isAbsolute) {
// 若是決定路徑,呼叫真正的執行方法
if (loadLibrary0(fromClass, new File(name))) {
return;
}
throw new UnsatisfiedLinkError("Can't load library: " + name);
}
if (loader != null) {
// 判斷當前類載入器及其雙親是否有該lib的類資訊
String libfilename = loader.findLibrary(name);
if (libfilename != null) {
File libfile = new File(libfilename);
if (!libfile.isAbsolute()) {
throw new UnsatisfiedLinkError(
"ClassLoader.findLibrary failed to return an absolute path: " + libfilename);
}
if (loadLibrary0(fromClass, libfile)) {
return;
}
throw new UnsatisfiedLinkError("Can't load " + libfilename);
}
}
// 查詢sys_paths路徑下是否有.dll檔案
for (int i = 0 ; i < sys_paths.length ; i++) {
File libfile = new File(sys_paths[i], System.mapLibraryName(name));
if (loadLibrary0(fromClass, libfile)) {
return;
}
libfile = ClassLoaderHelper.mapAlternativeName(libfile);
if (libfile != null && loadLibrary0(fromClass, libfile)) {
return;
}
}
// 查詢usr_paths路徑下是否有.dll檔案
if (loader != null) {
for (int i = 0 ; i < usr_paths.length ; i++) {
File libfile = new File(usr_paths[i],
System.mapLibraryName(name));
if (loadLibrary0(fromClass, libfile)) {
return;
}
libfile = ClassLoaderHelper.mapAlternativeName(libfile);
if (libfile != null && loadLibrary0(fromClass, libfile)) {
return;
}
}
}
// Oops, it failed
throw new UnsatisfiedLinkError("no " + name + " in java.library.path");
}
```
```java
private static boolean loadLibrary0(Class> fromClass, final File file) {
// Check to see if we're attempting to access a static library
// 檢視是否呼叫的lib為靜態連結庫
String name = findBuiltinLib(file.getName());
boolean isBuiltin = (name != null);
// 若是靜態連結庫則跳過,否則獲取file的路徑
if (!isBuiltin) {
boolean exists = AccessController.doPrivileged(
new PrivilegedAction