偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Java筑基 - JNI到底是個(gè)啥

開發(fā) 后端
在前面介紹Unsafe的文章中,簡(jiǎn)單的提到了java中的本地方法(Native Method),它可以通過(guò)JNI(Java Native Interface)調(diào)用其他語(yǔ)言中的函數(shù)來(lái)實(shí)現(xiàn)一些相對(duì)底層的功能,本文我們就來(lái)順藤摸瓜,介紹一下jni以及它的使用。

[[398657]]

在前面介紹Unsafe的文章中,簡(jiǎn)單的提到了java中的本地方法(Native Method),它可以通過(guò)JNI(Java Native Interface)調(diào)用其他語(yǔ)言中的函數(shù)來(lái)實(shí)現(xiàn)一些相對(duì)底層的功能,本文我們就來(lái)順藤摸瓜,介紹一下jni以及它的使用。

首先回顧一下jni的主要功能,從jdk1.1開始jni標(biāo)準(zhǔn)就成為了java平臺(tái)的一部分,它提供的一系列的API允許java和其他語(yǔ)言進(jìn)行交互,實(shí)現(xiàn)了在java代碼中調(diào)用其他語(yǔ)言的函數(shù)。通過(guò)jni的調(diào)用,能夠?qū)崿F(xiàn)這些功能:

通常情況下我們一般使用jni用來(lái)調(diào)用c或c++中的代碼,在上一篇文章中我們用了下面的流程來(lái)描述了native方法的調(diào)用過(guò)程:

  1. Java Code -> JNI -> C/C++ Code 

但是準(zhǔn)確的來(lái)說(shuō)這一過(guò)程并不嚴(yán)謹(jǐn),因?yàn)樽罱K被執(zhí)行的不是原始的c/c++代碼,而是被編譯連接后的動(dòng)態(tài)鏈接庫(kù)。因此我們將這個(gè)過(guò)程從單純的代碼調(diào)用層面上進(jìn)行升級(jí),將jni的調(diào)用過(guò)程提高到了jvm和操作系統(tǒng)的層面,來(lái)加點(diǎn)細(xì)節(jié)進(jìn)行一下完善:

看到這里,可能有的小伙伴就要提出疑問(wèn)了,不是說(shuō)java語(yǔ)言是跨平臺(tái)的嗎,這種與操作系統(tǒng)本地編譯的動(dòng)態(tài)鏈接庫(kù)進(jìn)行的交互,會(huì)不會(huì)使java失去跨平臺(tái)的可移植性?

針對(duì)這一問(wèn)題,大家可以回想一下以前安裝jdk的經(jīng)歷,在官網(wǎng)的下載列表中提供了各個(gè)操作系統(tǒng)的不同版本jdk,例如windows、linux、mac os版本等等,在這些jdk中,針對(duì)不同系統(tǒng)有著不同的jvm實(shí)現(xiàn)。而java語(yǔ)言的跨平臺(tái)性恰好是和它底層的jvm密不可分的,正是依靠不同的操作系統(tǒng)下不同版本jvm的“翻譯”工作,才能使編譯后的字節(jié)碼在不同的平臺(tái)下暢通無(wú)阻的運(yùn)行。

在不同操作系統(tǒng)下,c/c++或其他代碼生成的動(dòng)態(tài)鏈接庫(kù)也會(huì)有差異,例如在window平臺(tái)下會(huì)編譯為dll文件,在linux平臺(tái)下會(huì)編譯為so文件,在mac os下會(huì)編譯為jnilib文件。而不同平臺(tái)下的jvm,會(huì)“約定俗成”的去加載某個(gè)固定類型的動(dòng)態(tài)鏈接庫(kù)文件,使得依賴于操作系統(tǒng)的功能可以被正常的調(diào)用,這一過(guò)程可以參考下面的圖來(lái)進(jìn)行理解:

在對(duì)jni的整體調(diào)用流程有了一定的了解后,對(duì)于它如何調(diào)用其他語(yǔ)言中的函數(shù)這一過(guò)程,你是否也會(huì)好奇它是怎樣實(shí)現(xiàn)的,下面我們就通過(guò)手寫一個(gè)java程序調(diào)用c++代碼的例子,來(lái)理解它的調(diào)用過(guò)程。

1、準(zhǔn)備java代碼

首先定義一個(gè)包含了native方法的類如下,之后我們要使用這個(gè)類中的native方法通過(guò)jni調(diào)用c++編寫成的動(dòng)態(tài)鏈接庫(kù)中的方法:

  1. public class JniTest { 
  2.     static
  3.         System.loadLibrary("MyNativeDll"); 
  4.     } 
  5.  
  6.     public static native void callCppMethod(); 
  7.  
  8.     public static void main(String[] args) { 
  9.         System.out.println("DLL path:"+System.getProperty("java.library.path")); 
  10.         callCppMethod(); 
  11.     } 

在代碼中主要完成了以下工作:

在靜態(tài)代碼塊中,調(diào)用loadLibrary方法加載本地的動(dòng)態(tài)鏈接庫(kù),參數(shù)為不包含擴(kuò)展名的動(dòng)態(tài)鏈接庫(kù)庫(kù)文件名。在window平臺(tái)下會(huì)加載dll文件,在linux平臺(tái)下會(huì)加載so文件,在mac os下會(huì)加載jnilib文件

聲明了一個(gè)native方法,native關(guān)鍵字負(fù)責(zé)通知jvm這里調(diào)用方法的是本地方法,該方法在外部被定義

在main方法中,打印加載dll文件的路徑,并調(diào)用本地方法

2、生成頭文件

在使用c/c++來(lái)實(shí)現(xiàn)本地方法時(shí),需要先創(chuàng)建.h頭文件。簡(jiǎn)單的來(lái)說(shuō),c/c++程序通常由頭文件(.h)和定義文件(.c或.cpp)組成,頭文件包含了功能函數(shù)、數(shù)據(jù)接口的聲明,而定義文件用于書寫程序的實(shí)現(xiàn)。

在jdk8中可以直接使用javac -h指令生成c/c++語(yǔ)言中的頭文件。如果你使用的是較早版本的jdk,需要在執(zhí)行javac編譯完成class文件后,再執(zhí)行javah -jni生成c/c++風(fēng)格的頭文件(在jdk10的新特性中已經(jīng)刪除了javah這一指令)。我們使用的jdk8簡(jiǎn)化了這一步驟,使其可以一步完成,在命令行窗口下執(zhí)行命令:

  1. javac -h ./jni JniTest.java 

指令中使用 -h參數(shù)指定放置生成的頭文件的位置,最后的參數(shù)是java源文件的名稱。在這個(gè)過(guò)程中完成了兩件工作,首先生成class文件,其次在參數(shù)指定的目錄下生成頭文件。生成的頭文件com_cn_jni_JniTest.h內(nèi)容如下:

  1. /* DO NOT EDIT THIS FILE - it is machine generated */ 
  2. #include <jni.h> 
  3. /* Header for class com_cn_jni_JniTest */ 
  4.  
  5. #ifndef _Included_com_cn_jni_JniTest 
  6. #define _Included_com_cn_jni_JniTest 
  7. #ifdef __cplusplus 
  8. extern "C" { 
  9. #endif 
  10. /* 
  11.  * Class:     com_cn_jni_JniTest 
  12.  * Method:    callCppMethod 
  13.  * Signature: ()V 
  14.  */ 
  15. JNIEXPORT void JNICALL Java_com_cn_jni_JniTest_callCppMethod 
  16.   (JNIEnv *, jclass); 
  17.  
  18. #ifdef __cplusplus 
  19. #endif 
  20. #endif 

生成的頭文件和大家熟悉的 java接口有些相似,只有函數(shù)的聲明而沒(méi)有具體實(shí)現(xiàn)。簡(jiǎn)單的解釋一下頭文件中的代碼:

  • extern "C"告訴編譯器,這部分代碼使用C語(yǔ)言規(guī)則來(lái)進(jìn)行編譯
  • JNIEXPORT和JNICALL是jni中定義的兩個(gè)宏,使用JNIEXPORT支持在外部程序代碼中調(diào)用該動(dòng)態(tài)庫(kù)中的方法,使用JNICALL定義函數(shù)調(diào)用時(shí)參數(shù)的入棧出棧約定
  • 函數(shù)名稱由包名+類名+方法名組成,在該方法中有兩個(gè)參數(shù),通過(guò)第一個(gè)參數(shù)JNIEnv *的對(duì)象可以調(diào)用jni.h中封裝好的大量函數(shù) ,第二個(gè)參數(shù)代表著native方法的調(diào)用者,當(dāng)java代碼中定義的native方法是靜態(tài)方法時(shí)這里的參數(shù)是jclass,非靜態(tài)方法的參數(shù)是jobject

接下來(lái)我們創(chuàng)建一個(gè)cpp文件,引用頭文件并實(shí)現(xiàn)其中的函數(shù),也就是native方法將要實(shí)際執(zhí)行的邏輯:

  1. #include "com_cn_jni_JniTest.h" 
  2. #include <stdio.h> 
  3.   
  4. JNIEXPORT void JNICALL Java_com_cn_jni_JniTest_callCppMethod 
  5.   (JNIEnv *, jclass) 
  6.     printf("Print From Cpp: \n"); 
  7.     printf("I am a cpp method ! \n"); 

在方法的實(shí)現(xiàn)中加入簡(jiǎn)單的printf打印語(yǔ)句,在完成方法的實(shí)現(xiàn)后,我們需要將上面的cpp文件編譯為動(dòng)態(tài)鏈接庫(kù),提供給java中的native方法調(diào)用,因此下面需要在window環(huán)境下安裝gcc環(huán)境。

3、gcc環(huán)境安裝

在window環(huán)境下,如果你不希望為了生成一個(gè)dll就去下載體積龐大的的Visual Studio的話,MinGW是一個(gè)不錯(cuò)的選擇,簡(jiǎn)單的說(shuō)它就是一個(gè)windows版本下的gcc。那么估計(jì)有的同學(xué)又要問(wèn)了,gcc是什么?簡(jiǎn)單的來(lái)說(shuō)就是linux系統(tǒng)下C/C++的編譯器,通過(guò)它可以將源代碼編譯成可執(zhí)行程序。首先從下面的網(wǎng)址下載mingw-get-setup的安裝程序:

  1. http://sourceforge.net/projects/mingw/  #32位 
  2. https://sourceforge.net/projects/mingw-w64/  #64位 

需要注意,一定要按照系統(tǒng)位數(shù)安裝對(duì)應(yīng)的版本,否則后面生成的dll在運(yùn)行時(shí)就可能會(huì)因位數(shù)不匹配而報(bào)錯(cuò),我在實(shí)驗(yàn)的過(guò)程中第一次就錯(cuò)誤安裝了32位的MinGw,導(dǎo)致了在程序運(yùn)行過(guò)程中報(bào)了下面錯(cuò)誤:

  1. Exception in thread "main" java.lang.UnsatisfiedLinkError:  
  2. F:\Workspace20\unsafe-test\src\main\java\com\cn\jni\jni\MyNativeDll.dll:  
  3. Can't load IA 32-bit .dll on a AMD 64-bit platform 

安裝完成后,將MinGW\bin目錄加入系統(tǒng)環(huán)境變量PATH,輸入下面的指令測(cè)試gcc是否可以使用:

  1. gcc -v 

如果能夠正常輸出gcc的版本信息,說(shuō)明gcc安裝成功:

在測(cè)試的過(guò)程中發(fā)現(xiàn),如果安裝的是64位的mingw,那么在安裝完成后gcc就已經(jīng)直接可以可用。但是如果安裝的是32位的mingw,需要使用下面的命令單獨(dú)安裝gcc:

  1. mingw-get install gcc 

gcc安裝完成后,如果還想安裝gdb或make等其他指令進(jìn)行調(diào)試或編譯,同樣可以使用強(qiáng)大的mingw-get命令進(jìn)行獨(dú)立安裝。

4、生成動(dòng)態(tài)鏈接庫(kù)

在gcc環(huán)境準(zhǔn)備好的條件下,接下來(lái)使用下面的命令生成dll動(dòng)態(tài)鏈接庫(kù):

  1. gcc -m64 -Wl,--add-stdcall-alias -I"D:\Program Files\Java\jdk1.8.0_261\include"  
  2. -I"D:\Program Files\Java\jdk1.8.0_261\include\win32"  
  3. -shared -o MyNativeDll.dll JniTestImpl.cpp 

簡(jiǎn)單的解釋一下各個(gè)參數(shù)的含義:

  • -m64 :將cpp代碼編譯為64位的應(yīng)用程序
  • -Wl,--add-stdcall-alias:-Wl表示將后面的參數(shù)傳遞給連接程序,參數(shù)--add-stdcall-alias表示帶有標(biāo)準(zhǔn)調(diào)用后綴@NN的符號(hào)會(huì)被剝掉后綴后導(dǎo)出
  • -I:指定頭文件的路徑,在生成的頭文件代碼中引入的jni.h就在這個(gè)目錄下
  • -shared:指定生成動(dòng)態(tài)鏈接庫(kù),如果不使用這個(gè)標(biāo)志那么外部程序?qū)o(wú)法連接
  • -o:指定目標(biāo)的名稱,這里將生成的動(dòng)態(tài)鏈接庫(kù)命名為MyNativeDll.dll
  • JniTestImpl.cpp:被編譯的源程序文件名

在指令的執(zhí)行過(guò)程中,都做了什么事呢,可以參考下面這張圖:

在執(zhí)行過(guò)程中,以.cpp源代碼和.h頭文件作為源文件,先進(jìn)行了預(yù)處理、編譯、匯編的操作,圖中省略了這一階段產(chǎn)生的一些中間文件,編譯完成后生成的.o二進(jìn)制文件相對(duì)重要,依賴這個(gè)文件,最終生成動(dòng)態(tài)鏈接庫(kù)。

在執(zhí)行了上面的指令后,就會(huì)在當(dāng)前目錄下生成一個(gè)MyNativeDll.dll文件,再運(yùn)行之前準(zhǔn)備好的java代碼:

程序報(bào)錯(cuò),這是因?yàn)樵谀J(rèn)的載入庫(kù)文件的目錄下沒(méi)有找到我們的dll文件。有兩種方式可以解決:

直接將dll文件拷貝到默認(rèn)的加載目錄下,具體的路徑可以通過(guò)System.getProperty("java.library.path")獲取,該方法可能會(huì)獲得多個(gè)目錄,放在任意一個(gè)目錄下即可

是在VM Option中修改啟動(dòng)參數(shù),指定dll的存放目錄:

  1. -Djava.library.path=F:\Workspace20\unsafe-test\src\main\java\com\cn\jni\jni 

再次執(zhí)行,輸出結(jié)果:

  1. DLL path:F:\Workspace20\unsafe-test\src\main\java\com\cn\jni\jni 
  2. Print From Cpp:  
  3. I am a cpp method ! 

可以看到程序加載dll的路徑已經(jīng)切換成了它的存放路徑,并且通過(guò)jni調(diào)用成功,輸出了在c++中的代碼邏輯??梢杂孟旅娴膱D來(lái)總結(jié)上面實(shí)現(xiàn)jni調(diào)用的過(guò)程:

在對(duì)jni的調(diào)用有了一個(gè)整體的了解后,如果大家對(duì)代理模式比較熟悉的話,也可以從代理模式的角度來(lái)理解jni,將jni調(diào)用過(guò)程中的各個(gè)角色帶入到代理模式中:

  • 代理角色:包含native方法的jni類
  • 實(shí)現(xiàn)角色:c/c++或其他語(yǔ)言實(shí)現(xiàn)的動(dòng)態(tài)鏈接庫(kù)
  • 客戶端:調(diào)用native方法的java類程序
  • 接口(抽象角色):在jni中接口這一角色的存在感相對(duì)薄弱,因?yàn)閖ni是跨語(yǔ)言的,所以說(shuō)無(wú)法嚴(yán)格的定義一個(gè)接口并讓它同時(shí)應(yīng)用于java和其他語(yǔ)言。但是通過(guò)生成的.h頭文件,在一定程度上實(shí)現(xiàn)了從接口規(guī)范上統(tǒng)一了java中native方法和其他語(yǔ)言中的函數(shù)

以代理模式的概述圖來(lái)進(jìn)行描述:

上圖在標(biāo)準(zhǔn)代理模式的基礎(chǔ)上做了一些修改以便于理解,因?yàn)檫@里的接口只做規(guī)范約束作用,所以讓客戶端的調(diào)用過(guò)程跳過(guò)了接口,直接指向了代理角色,再由代理角色調(diào)用實(shí)現(xiàn)角色完成功能的調(diào)用??偟膩?lái)說(shuō),jni起到了一個(gè)代理或中介的作用,與常見(jiàn)代理不同的是這里只做方法的調(diào)用,而不實(shí)現(xiàn)邏輯上的增強(qiáng)。通過(guò)這一模式,向java程序員隱藏了底層c/c++代碼的實(shí)現(xiàn)細(xì)節(jié),讓我們專注于業(yè)務(wù)代碼的編寫即可。

總結(jié)

在前面對(duì)native方法有了一定了解的基礎(chǔ)上,本文介紹了jni的相關(guān)知識(shí)。通過(guò)本文的學(xué)習(xí),有助于我們:

 

  • 理解java的為何能夠做到跨平臺(tái),以及依賴操作系統(tǒng)的底層操作是如何實(shí)現(xiàn)的
  • 了解native方法的調(diào)用過(guò)程,在必要時(shí)可以自己實(shí)現(xiàn)jni類接口調(diào)用
  • 回顧一些C/C++知識(shí)
  • 當(dāng)然了,使用jni也會(huì)帶來(lái)一些缺點(diǎn):
  • 當(dāng)在某個(gè)操作系統(tǒng)下使用了jni標(biāo)準(zhǔn),將本地代碼編譯生成了動(dòng)態(tài)鏈接庫(kù)后,如果要將這個(gè)程序移植到其他操作系統(tǒng),需要在新的平臺(tái)重新編譯代碼生成動(dòng)態(tài)鏈接庫(kù)
  • 對(duì)其他語(yǔ)言的不正確使用可能會(huì)造成程序出現(xiàn)錯(cuò)誤,例如之前提到的使用c語(yǔ)言進(jìn)行內(nèi)存操作時(shí)未及時(shí)回收內(nèi)存可能引起的內(nèi)存泄漏
  • 對(duì)其他語(yǔ)言的依賴過(guò)高,會(huì)提高了java和其他語(yǔ)言的耦合性,也提高了對(duì)項(xiàng)目代碼的維護(hù)成本

本文轉(zhuǎn)載自微信公眾號(hào)「碼農(nóng)參上」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系碼農(nóng)參上公眾號(hào)。

 

責(zé)任編輯:武曉燕 來(lái)源: 碼農(nóng)參上
相關(guān)推薦

2024-02-07 12:35:00

React并發(fā)模式concurrent

2022-05-04 08:38:32

Netty網(wǎng)絡(luò)框架

2021-01-28 17:41:32

Github網(wǎng)站Pull Reques

2022-04-10 19:26:07

TypeScript類型語(yǔ)法

2024-07-12 15:08:23

Python@wraps函數(shù)

2021-12-26 00:01:51

Log4Shell漏洞服務(wù)器

2024-08-01 17:34:56

Promiseaxios請(qǐng)求

2021-12-16 15:11:59

Facebook天秤幣加密貨幣

2022-09-06 21:38:45

數(shù)字人數(shù)字孿生

2024-08-26 14:23:56

2013-05-29 10:17:56

Hadoop分布式文件系統(tǒng)

2012-07-25 09:09:46

GNOME OS桌面

2024-02-26 00:00:00

人工智能序列數(shù)據(jù)機(jī)器人

2020-03-07 09:47:48

AVL樹算法場(chǎng)景

2020-10-29 07:03:56

Docker容器存儲(chǔ)

2024-02-01 20:15:37

2021-12-16 21:13:38

通信網(wǎng)管平臺(tái)

2025-05-28 00:30:00

MCP智能體Agent

2019-10-28 09:59:26

區(qū)塊鏈技術(shù)智能

2017-03-16 15:28:20

人工智能視覺(jué)識(shí)別
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)