前言
最近在學習JNI的相關知識些阅,即Java Native Interface,它提供了若干API使得Java和C/C++的通信成為可能。我們知道,Java代碼運行于Java虛擬機中宇姚,獨立于某個平臺,這也是Java的可移植性的優(yōu)點夫凸。而C/C++代碼運行于Windows或Linux平臺浑劳。為了實現(xiàn)Java和其他代碼的交互,JNI應運而生夭拌。最簡單的就是魔熏,就是你在java中聲明一個方法,但方法的具體實現(xiàn)是由C/C++代碼來實現(xiàn)的鸽扁,然后生成dll庫或者so庫蒜绽,java層通過引入這個庫而調(diào)用這個Native方法,也就是我們在Android中會遇到的Native方法桶现。
JNI的優(yōu)點
1滓窍、JNI能調(diào)用本地操作系統(tǒng)所提供的本地方法,調(diào)用系統(tǒng)級的接口巩那。(dll庫是windows平臺吏夯,而so庫是Linux平臺)
2、對于一些以前用C/C++實現(xiàn)過的庫即横,可以用JNI來進行調(diào)用噪生,而不用重新在Java層實現(xiàn)一次,節(jié)省時間东囚。
3跺嗽、對于某些特殊場景,需要高效率地執(zhí)行代碼页藻,比如圖形渲染的過程桨嫁,這時候顯然使用C/C++能極大提高運行速度。
JNI的缺點
使用了JNI與本地方法交互之后份帐,犧牲了Java的可移植性這一優(yōu)點璃吧,因為windwos和linux是不同的形式的庫,必須在具體平臺下重新編譯废境。
筆者的環(huán)境平臺
在開始之前畜挨,先說一下筆者所使用的IDE以及平臺
1筒繁、Windows 10操作系統(tǒng)
2、IntelliJ IDEA Community Edition 2018.3.5 x64
3巴元、Visual Studio 2017
4毡咏、JDK10
從Hello World開始
好了,說了一大堆逮刨,那么就讓我們開始學習JNI呕缭,先從最簡單的Hello World開始,先讓代碼跑起來修己,然后再繼續(xù)深化學習臊旭。
1、在Intellij新建一個java項目
①NativeTest類箩退,里面僅聲明了一個Native方法:
native關鍵字表示這個方法由native層來實現(xiàn)离熏,我們在java層不需要關注它的實現(xiàn)。
②Test類戴涝,這里聲明了main方法滋戳,并調(diào)用NativeTest的native方法。
2啥刻、為native方法所在的類生成相應的頭文件
上面的native方法在NativeTest類中奸鸯,我們需要為此生成一個.h文件,只有這樣我們才能把.h文件的方法用C/C++代碼去實現(xiàn)可帽。那么娄涩,怎么生成一個.h文件呢?JDK為我們提供了工具專門用于生成JNI所用的頭文件映跟。
格式是:javac -h <directory> <source file>
<directory>表示生成的.h文件所放置的位置蓄拣。
<source file>表示待編譯的源文件。
上面的命令需要在命令行中使用努隙,我們可以用Intellij提供的Terminal去輸入球恤,也可以使用windows的cmd去輸入,二者的效果是一樣的荸镊。為了方便起見咽斧,筆者這里使用的是Terminal。
2.1躬存、首先张惹,我們在Terminal定位到NativeTest類所在的文件夾,即:
2.2岭洲、然后宛逗,通過使用javac -h命令,編譯NativeTest類钦椭,并生成與這個類有關的.h文件:
注意:這里的<directory>使用了"."拧额,表示在當前目錄直接生成.h文件。<source file>這里填入NativeTest.java彪腔,因為當前目錄下有NativeTest.java文件侥锦,所以不需要添加額外的路徑了。
2.3德挣、運行之后恭垦,會發(fā)現(xiàn)當前目錄下多了兩個文件:NativeTest.class和com_jni_NativeTest.h文件:
所以我們知道,javac -h實際上做了兩部操作格嗅,對NativeTest.java進行編譯生成class文件番挺,然后再生成.h文件。
3屯掖、新建一個windows桌面程序項目
這一步玄柏,在VS 2017中進行,新建一個項目如下圖所示:
我們這里選擇動態(tài)鏈接庫(DLL)贴铜,因為我們需要這個dll庫供java層使用粪摘,這里項目的位置可以保存在任何一個地方。
3.1绍坝、項目創(chuàng)建完畢之后徘意,我們把剛才生成的com_jni_NativeTest.h文件復制到當前項目的文件目錄下,如下圖:
然后在VS2017的“解決方法資源管理器”中轩褐,在“頭文件選項”椎咧,右鍵選擇添加已有項,選中當前目錄下剛才復制進來的com_jni_NativeTest.h文件:
3.2把介、接下來勤讽,我們需要把一些額外的文件,打開jdk的安裝目錄(筆者jdk的安裝目錄為:C:\Program Files\Java\jdk-10)拗踢,在include文件夾下地技,復制jni.h和include\win32內(nèi)的jni_md.h 這兩個文件到NativeCode項目,即剛才存放com_jni_NativeTest.h文件的目錄秒拔,同時把這兩個文件作為現(xiàn)有項添加到頭文件莫矗,步驟3.1的最后。
完成3.1和3.2之后砂缩,我們會看到有如下的頭文件結(jié)構(gòu):
3.3作谚、打開項目中的com_jni_NativeTest.h文件,把頂部的#include<jni.h>改成#include"jni.h"庵芭,這樣就不會報錯了妹懒。解釋一下為什么要做這樣的改動:#include<>形式表示C/C++文件編譯時,首先從編譯器的類庫路徑里面尋找該頭文件双吆;而#include""表示在當前文件目錄下尋找頭文件眨唬。
3.4会前、關于com_jni_NativeTest.h文件的補充說明。
我們打開這個頭文件匾竿,觀察其內(nèi)部結(jié)構(gòu):
我們可以看到瓦宜,該頭文件聲明了一個方法Java_com_jni_NativeTest_sayHello(JNIEnv,jobject)岭妖,該方法有兩個關鍵字分別為JNIEXPORT和JNICALL 表示這個方法是要從Java層被調(diào)用的临庇。然后該函數(shù)有兩個形參:JNIEnv** 和 jobject。這兩個參數(shù)是native方法自帶的參數(shù)昵慌。
JNIEnv *是一個函數(shù)指針假夺,它指向一系列JNI提供的函數(shù)來進行數(shù)據(jù)操作。
jobject表示調(diào)用這個函數(shù)的對象斋攀,因為在java層它是一個實例方法已卷,所以實際上這個參數(shù)的作用類似于 this關鍵字。
4淳蔼、新建一個c++文件悼尾,實現(xiàn).h文件所聲明的函數(shù)
這里筆者直接使用VS幫我們生成的NativeCode.cpp文件進行操作:
這里僅簡單輸出了hello world。
5肖方、編譯生成DLL文件
這里要使用x64的Debug調(diào)試器(默認是x86)闺魏,點擊上方的生成——>生成解決方案,可以觀察到控制臺輸出了信息俯画,并標明了生成的dll文件所在的位置析桥,我們前往該位置,一般在 /項目目錄/x64/debug/NativeCode.dll艰垂。我們復制這個庫文件到Java項目內(nèi)泡仗。在這里,筆者在java項目跟目錄下猜憎,新建了一個native_libs的文件夾娩怎,把dll文件復制到這里:
6、加載DLL文件胰柑,并運行Main函數(shù)
DLL文件已經(jīng)被添加到我們的Java項目了截亦,接下來的操作就是要在JVM運行時加載它,以便我們后續(xù)調(diào)用native方法柬讨。我們在Test.java文件作點修改:
好了崩瓤,到目前為之,所有的工作都已經(jīng)做完踩官,讓我們運行一下Main函數(shù)却桶,看一下效果如何?
如果你看到了上面的輸出,那么恭喜你颖系,你已經(jīng)掌握了JNI調(diào)用的基本方法嗅剖!
小結(jié)
上面詳述了實現(xiàn)一個簡單JNI調(diào)用的步驟,現(xiàn)在小結(jié)一下整個流程嘁扼。
(1)在一個Java類中聲明native方法信粮。
(2)利用javac -h命令以該類為源文件生成一個.h文件
(3)在C/C++文件中實現(xiàn)該頭文件所聲明的方法
(4)編譯C/C++文件,生成一個DLL或so文件偷拔,把它添加到java項目中
(5)通過System.loadLibrary方法加載這個庫文件
踩過的坑
下面談談筆者在學習過程中遇到的一些問題蒋院,避免各位讀者再度踩坑亏钩。
1莲绰、關于javac -h和javah命令
筆者在剛學習JNI的知識時,在生成.h文件階段姑丑,在網(wǎng)上查的教程都是利用javah命令來進行操作蛤签。然而在筆者的電腦總提示"javah不是內(nèi)部命令",然而javac是可以正常運作的栅哀,這說明并不是環(huán)境變量出了問題震肮,這就有可能是jdk安裝目錄下壓根就沒有javah.exe,經(jīng)過查找留拾,確實是沒有這個文件戳晌,所以javah命令會運行失敗。
那么問題來了痴柔,為什么我的JDK沒有javah.exe呢沦偎?
經(jīng)過查閱資料,原來在JDK10以上的JDK內(nèi)部已經(jīng)去除了javah命令咳蔚,它的功能被整合進了javac -h命令內(nèi)豪嚎。然而在jdk8以下的jdk是有該命令的。
解決途徑:如果電腦安裝的是jdk10以及10以上谈火,使用javac -h命令侈询;而jdk8以及8以下的使用javah -jni命令。
2糯耍、javac命令的進一步說明
javac命令是編譯命令扔字,將java源文件編譯成class字節(jié)碼文件。我們可以在命令行輸入javac -help温技,了解它的使用方法:
其基本語法為:javac <options> <source files>啦租,其中<options>可以是多個,而且<source files>也可以是多個荒揣,這時候表示把多個源文件同時編譯篷角。所以生成.h文件的命令格式為:javac -h <directory> <source files>
需要注意的是:如果少了<directory>選項(如果是當前目錄直接用" . "代替),就會報錯系任,筆者曾在這里卡了很長時間恳蹲。
這篇文章到這里就結(jié)束了虐块,希望對各位同學有所裨益:) 謝謝看到這里的你。下一篇文章將會圍繞JNI的數(shù)據(jù)操作嘉蕾、函數(shù)操作部分進行詳細講解贺奠。