接續(xù)上個(gè)系列C語(yǔ)言基礎(chǔ)及指針
引言
在學(xué)習(xí)了C語(yǔ)言基礎(chǔ)之后 ,我們簡(jiǎn)單的了解了C語(yǔ)言編程的一些范式 , 了解了指針 , 結(jié)構(gòu)體 , 聯(lián)合體 , 函數(shù) , 文件IO等等 。我們最終的目的是要學(xué)會(huì)NDK開(kāi)發(fā) , 而NDK開(kāi)發(fā)就離不開(kāi)我們的JNI技術(shù) 。下面 , 就來(lái)開(kāi)始我們的JNI之旅吧 。
JNI的概念
JNI全稱(chēng) Java Native Interface , java本地化接口 , 可以通過(guò)JNI調(diào)用系統(tǒng)提供的API , 我們知道 , 不管是linux還是windows亦或是mac os , 這些操作系統(tǒng) , 都是依靠C/C++寫(xiě)出來(lái)的 , 還包括一些匯編語(yǔ)言寫(xiě)的底層硬件驅(qū)動(dòng) 。java和C/C++不同 , 它不會(huì)直接編譯成平臺(tái)機(jī)器碼,而是編譯成虛擬機(jī)可以運(yùn)行的java字節(jié)碼的.class文件,通過(guò)JIT技術(shù)即時(shí)編譯成本地機(jī)器碼,所以有效率就比不上C/C++代碼,JNI技術(shù)就解決了這一痛點(diǎn),下面我們來(lái)看看JNI調(diào)用示意圖:

從上圖可以得知 ,JNI技術(shù)通過(guò)JVM調(diào)用到各個(gè)平臺(tái)的API , 雖然JNI可以調(diào)用C/C++ , 但是JNI調(diào)用還是比C/C++編寫(xiě)的原生應(yīng)用還是要慢一點(diǎn) , 不過(guò)對(duì)高性能計(jì)算來(lái)說(shuō) , 這點(diǎn)算不得什么 , 享受它的便利 , 也要承擔(dān)它的弊端 。
JNI開(kāi)發(fā)流程
因?yàn)镴NI開(kāi)發(fā)并未涉及到NDK , 所有我們的開(kāi)發(fā)工具是eclipse and visual studio 。
第一步:編寫(xiě)java本地方法
public static native String getStringFromC() ;
第二步:生成.h頭文件 , 需要使用到的java命令是javah
# 假定你以配置好java環(huán)境
# 在控制臺(tái)中進(jìn)入工程src目錄
> cd E:\java_workspace\Hello_JNI\src
> javah com.zeno.jni.HelloJni
# 需要注意的是 , com.zeno.jni.HelloJni , 是全類(lèi)名, 包名.類(lèi)名
第三步:將.h頭文件復(fù)制到VS的代碼文件目錄下 , 在解決方案中的頭文件目錄-> 右鍵-> 添加 -> 添加現(xiàn)有項(xiàng) 。 將我們的頭文件添加進(jìn)來(lái)。

添加進(jìn)來(lái)之后 , 打開(kāi)我們的頭文件 , 會(huì)發(fā)現(xiàn) , 頭文件里面的jni.h這個(gè)頭文件找不到 , 因?yàn)樗皇窍到y(tǒng)的頭文件 , 所有我們需要將它找出來(lái) , 并復(fù)制到我們的工程項(xiàng)目中 。
# JDK 的jni.h頭文件目錄
D:\Java\jdk1.8.0_60\include\jni.h
# 在jni.h頭文件中,又引入了jni_md.h頭文件 , 所有這個(gè)我們也要一并賦值過(guò)來(lái)
D:\Java\jdk1.8.0_60\include\win32\jni_md.h
將jni.h這個(gè)頭文件按照上述步驟 , 添加到頭文件目錄中 , 注意將<>改成" " , <>表示引入的是系統(tǒng)頭文件," "表示引入的是第三方頭文件。
第四步:實(shí)現(xiàn)頭文件
// 生成的頭文件函數(shù)
/*
* Class: com_zeno_jni_HelloJni
* Method: getStringFormC
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_zeno_jni_HelloJni_getStringFromC
(JNIEnv *, jclass);
新建一個(gè).c的文件 ,引入我們生成的頭文件 ,然后實(shí)現(xiàn)我們生成的C語(yǔ)言函數(shù)。
/*
* Class: com_zeno_jni_HelloJni
* Method: getStringFormC
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_zeno_jni_HelloJni_getStringFromC
(JNIEnv *Env, jclass jclazz) {
return (*Env)->NewStringUTF(Env, "Jni C String");
}
第五步:生成動(dòng)態(tài)鏈接庫(kù)
補(bǔ)充:
| 庫(kù)名稱(chēng) | 特性 | 擴(kuò)展名 |
|---|---|---|
| 動(dòng)態(tài)庫(kù) | 1.動(dòng)態(tài)庫(kù)把對(duì)一些庫(kù)函數(shù)的鏈接載入推遲到程序運(yùn)行的時(shí)期。2.可以實(shí)現(xiàn)進(jìn)程之間的資源共享。(因此動(dòng)態(tài)庫(kù)也稱(chēng)為共享庫(kù))3.可以動(dòng)態(tài)注入到程序中 | win(.dll)linux(.so) |
| 靜態(tài)庫(kù) | 1.靜態(tài)庫(kù)對(duì)函數(shù)庫(kù)的鏈接是放在編譯時(shí)期完成的。2.程序在運(yùn)行時(shí)與函數(shù)庫(kù)再無(wú)瓜葛,移植方便。3.浪費(fèi)空間和資源,因?yàn)樗邢嚓P(guān)的目標(biāo)文件與牽涉到的函數(shù)庫(kù)被鏈接合成一個(gè)可執(zhí)行文件。 | win(.lib)linux(.a) |
了解了靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù) , 下面我們就來(lái)生成一個(gè)動(dòng)態(tài)庫(kù),以VS為例:
選中項(xiàng)目 -> 右鍵 -> 屬性 -> 常規(guī) -> 項(xiàng)目默認(rèn)值 -> 配置類(lèi)型 , 選擇動(dòng)態(tài)庫(kù).dll

配置完成之后 , 選中項(xiàng)目 -> 生成 。即可生成動(dòng)態(tài)鏈接庫(kù)。

第六步:配置環(huán)境變量
我們生成了.dll文件之后 , java環(huán)境并不知道有這個(gè).dll動(dòng)態(tài)鏈接庫(kù)的存在 , 所有我們需要將生成.dll的文件目錄 , 配置到環(huán)境變量中。

注意:配置好環(huán)境變量 , 需要重新啟動(dòng)eclipse ,不然會(huì)找不到動(dòng)態(tài)鏈接庫(kù)的。
第七步:加載動(dòng)態(tài)鏈接庫(kù)
static{
System.loadLibrary("Hello_JNI") ;
}
第八步 : 調(diào)用本地方法并執(zhí)行
/**
*
* @author Zeno
*
* JNI (Java Native Interface) java本地化接口
*
* Android Framework層與Native層相互通信的基石
*
*
*/
public class HelloJni {
// java調(diào)用C/C++函數(shù)的本地方法
public static native String getStringFromC() ;
public static void main(String[] args) {
System.out.println("getStringFormC == "+getStringFromC());
}
static{
// 加載動(dòng)態(tài)庫(kù)
System.loadLibrary("Hello_JNI") ;
}
}
輸出:
getStringFormC == Jni C String
JNI 開(kāi)發(fā)的步驟雖然多 , 但大多比較簡(jiǎn)單 , 操作起來(lái)不難 。
結(jié)語(yǔ)
這篇是JNI系列的開(kāi)篇 , 總體來(lái)說(shuō)比較簡(jiǎn)單 , 先將流程走順了 , 后續(xù)的開(kāi)發(fā)就會(huì)好很多 , 流程性的東西 , 需要多操作 , 就會(huì)形成固定的套路 。