####引言 在學習了C語言基礎以後 ,咱們簡單的瞭解了C語言編程的一些範式 , 瞭解了指針 , 結構體 , 聯合體 , 函數 , 文件IO等等 。咱們最終的目的是要學會NDK開發 , 而NDK開發就離不開咱們的JNI技術 。下面 , 就來開始咱們的JNI之旅吧 。 ####JNI的概念 JNI全稱 Java Native Interface , java本地化接口 , 能夠經過JNI調用系統提供的API , 咱們知道 , 無論是linux仍是windows亦或是mac os , 這些操做系統 , 都是依靠C/C++寫出來的 , 還包括一些彙編語言寫的底層硬件驅動 。java和C/C++不一樣 , 它不會直接編譯成平臺機器碼,而是編譯成虛擬機能夠運行的java字節碼的.class文件,經過JIT技術即時編譯成本地機器碼,因此有效率就比不上C/C++代碼,JNI技術就解決了這一痛點,下面咱們來看看JNI調用示意圖: java
從上圖能夠得知 ,JNI技術經過JVM調用到各個平臺的API , 雖然JNI能夠調用C/C++ , 可是JNI調用仍是比C/C++編寫的原生應用仍是要慢一點 , 不過對高性能計算來講 , 這點算不得什麼 , 享受它的便利 , 也要承擔它的弊端 。linux
####JNI開發流程以下:編程
1.編寫native方法 2.javah方法,生成.h頭文件 3.複製.h頭文件到CPP工程中 4.複製jni.h和jni_md.h文件到CPP工程中 5.實現.h頭文件聲明的函數 6.生成dll文件 7.java項目引入該dll文件 8.加載動態連接庫 9.運行java類windows
####1.編寫native方法bash
public class JniTest {
public native static String getStringFromC();
public static void main(String[] args){
String text= getStringFromC();
System.out.printf(text);
}
}
複製代碼
####2.調用java 中javah方法,生成.h頭文件 打開命令行,經過cd命令轉到當前Java工程的src目錄下面,而後執行javah命令,參數是完整類名:函數
D:\IdeaProjects\jni1\src>javah com.haocai.jni.JniTest
複製代碼
#####當文件中含有中文時可能出現源碼分析
錯誤: 編碼GBK的不可映射字符
複製代碼
因此能夠指定編碼格式,解決該問題性能
D:\IdeaProjects\jni1\src>javah -jni -encoding UTF-8 com.haocai.jni.JniTest
複製代碼
而後在src目錄就會生成.h頭文件: 學習
####3.複製.h頭文件到CPP工程中 將.h頭文件複製到VS的代碼文件目錄下 , 在解決方案中的頭文件目錄-> 右鍵-> 添加 -> 添加現有項 。 將咱們的頭文件添加進來。ui
####4.複製jni.h和jni_md.h文件到CPP工程中
# JDK 的jni.h頭文件目錄
D:\Java\jdk1.8.0_60\include\jni.h
# 在jni.h頭文件中,又引入了jni_md.h頭文件 , 全部這個咱們也要一併賦值過來
D:\Java\jdk1.8.0_60\include\win32\jni_md.h
複製代碼
將jni.h這個頭文件按照上述步驟 , 添加到頭文件目錄中 , 注意將<>改爲" " , <>表示引入的是系統頭文件," "表示引入的是第三方頭文件。
####5.實現.h頭文件中聲明的函數。
com_haocai_jni_JniTest.h內容
#include <jni.h>
/* Header for class com_haocai_jni_JniTest */
#ifndef _Included_com_haocai_jni_JniTest
#define _Included_com_haocai_jni_JniTest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_haocai_jni_JniTest
* Method: getStringFromC
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_haocai_jni_JniTest_getStringFromC
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
複製代碼
com_haocai_jni_JniTest.h中頭文件函數
/*
* Class: com_haocai_jni_JniTest
* Method: getStringFromC
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_haocai_jni_JniTest_getStringFromC
(JNIEnv *, jclass);
複製代碼
#####新建一個.c的文件 ,引入咱們生成的頭文件 ,而後實現咱們生成的C語言函數。
#include "com_haocai_jni_JniTest.h"
//函數實現
JNIEXPORT jstring JNICALL Java_com_haocai_jni_JniTest_getStringFromC
(JNIEnv *env, jclass jcls) {
//JNIEnv 結構體指針
//env二級指針
//表明Java運行環境,調用Java中的代碼
//簡單實現
//將C的字符串轉爲一個java字符串
return (*env)->NewStringUTF(env,"String From C");
}
複製代碼
####注意:JNI開發中JNIEnv在C和C++中實現的區別
JNIEnv:JNIEnv裏面有不少方法,與Java進行交互,表明Java的運行環境。JNI Environment。
#####在C中:
JNIEnv 結構體指針的別名 env 二級指針
JNIEXPORT jstring JNICALL Java_com_test_JniTest_getStringFromC
(JNIEnv * env, jclass jcls){
//env是一個二級指針,函數中須要再次傳入
return (*env)->NewStringUTF(env, "String From C");
}
複製代碼
#####在C++中:
JNIEnv 是一個結構體的別名 env 一級指針
JNIEXPORT jstring JNICALL Java_com_test_JniTest_getStringFromC
(JNIEnv * env, jclass jcls){
//env是一個一級指針,函數中不須要再次傳入
return env->NewStringUTF("String From C");
}
複製代碼
爲何要用二級指針:
爲何須要傳入JNIEnv?函數執行過程當中須要JNIEnv
C++爲何沒有傳入?由於C++中有this指針。 C++只是對C的那一套進行的封裝,給一個變量賦值爲指針,這個變量是二級指針 源碼分析
在jni.h頭文件中有下面的預編譯代碼:
#ifdef __cplusplus
typedef JNIEnv_ JNIEnv;
#else
typedef const struct JNINativeInterface_ *JNIEnv;
#endif
複製代碼
若是是C環境的話,JNIEnv就是一個JNINativeInterface_結構體的指針別名。 若是是C++環境的話,JNIEnv就是一個結構體JNIEnv_的別名,而JNIEnv_是對JNINativeInterface_的封裝。 #####C語言中結構體與指針相關知識回顧 Android NDK開發之旅6--C語言--結構體
struct Man
{
char name[20];
int age;
};
void main() {
struct Man m1 = { "Jack",30 };
//結構體指針
struct Man *p = &m1;
printf(" %s, %d\n", m1.name, m1.age);
printf(" %s, %d\n",(*p).name,(*p).age);
//"->"箭頭是"(*P)."的簡寫
printf(" %s, %d\n", p->name, p->age);
system("pause");
}
結果輸出:
Jack, 30
Jack, 30
Jack, 30
複製代碼
#####C語言中NewStringUTF函數,大體實現以下:
#include <stdio.h>
//JNIEnv結構體的指針別名
typedef struct JNINativeInterface_* JNIEnv;
//結構體
struct JNINativeInterface_
{
//函數指針
char* (*NewStringUTF)(JNIEnv*, char*);
};
//函數實現
char* NewStringUTF(JNIEnv* env,char* str) {
//NewStringUTF執行過程,仍然須要用到JNIEnvironment
return str;
}
void main() {
//實例化結構體
struct JNINativeInterface_ struct_env;
struct_env.NewStringUTF = NewStringUTF;
//結構體指針
JNIEnv e = &struct_env;
//結構體的二級指針
JNIEnv *env = &e;
//經過二級指針調用函數
char* str = (*env)->NewStringUTF(env, "abc");
printf("%s", str);
getchar();
}
結果輸出:
abc
複製代碼
也就是說,咱們在C語音調用下面這句的時候:
(*env)->NewStringUTF(env, "String From C");
複製代碼
env是結構體的二級指針,它取內容*env是一級指針,經過一級指針就能夠經過->符號操做結構體了。 而NewStringUTF函數中須要用到JNIEnvironment,所以須要繼續傳入這個二級指針env自身。
可是在C++裏面,JNIEnv就是一個結構體的別名,經過使用一級指針一樣能夠訪問結構體自己,可是因爲C++裏面有this關鍵字表明自身,所以能夠省略傳入參數(已經封裝好)。
struct JNIEnv_ {
const struct JNINativeInterface_ *functions;
jstring NewStringUTF(const char *utf) {
return functions->NewStringUTF(this,utf);
}
//代碼省略
}
複製代碼
如上所示,C++中的JNIEnv就是JNIEnv_的別名,而JNIEnv_是對JNINativeInterface_的一次封裝,在函數調用的時候,最終仍是調用JNINativeInterface_結構體的方法:
functions->NewStringUTF(this, utf);
複製代碼
其中,原本JNINativeInterface_的函數NewStringUTF就是須要傳入二級指針的,由於C++中有this指針,表明着調用這functions的指針(其實就是二級指針),所以在C++中能夠直接使用this指針表明當前調用者的指針(二級指針),而在C語音中就須要有二級指針去作了。
####6.生成動態連接庫dll文件
庫名稱 | 特性 | 擴展名 |
---|---|---|
動態庫 | 1.動態庫把對一些庫函數的連接載入推遲到程序運行的時期 2.能夠實現進程之間的資源共享。(所以動態庫也稱爲共享庫) 3.能夠動態注入到程序中 |
win(.dll)linux(.so) |
靜態庫 | 1.靜態庫對函數庫的連接是放在編譯時期完成的。 2.程序在運行時與函數庫再無瓜葛,移植方便。 3.浪費空間和資源,由於全部相關的目標文件與牽涉到的函數庫被連接合成一個可執行文件。 |
win(.lib)linux(.a) |
瞭解了靜態庫和動態庫 , 下面咱們就來生成一個動態庫,以VS爲例:
選中項目 -> 右鍵 -> 屬性 -> 常規 -> 項目默認值 -> 配置類型 , 選擇動態庫.dll
複製代碼
配置完成以後 , 選中項目 -> 生成 。便可生成動態連接庫.dll文件。
####7.java項目引入該dll文件
####8.加載動態連接庫
//加載動態庫
static{
System.loadLibrary("jni1");
}
複製代碼
####9.運行java類
public class JniTest {
public native static String getStringFromC();
public static void main(String[] args){
String text= getStringFromC();
System.out.printf(text);
}
//加載動態庫
static{
System.loadLibrary("jni1");
}
}
複製代碼
####輸出:
String From C
複製代碼
####結語
#####這篇是JNI系列的開篇 , 其實流程比較簡單。可是咱們要了解JNIEnv其後的結構體與指針知識,爲後續深刻學習JNI打下基礎。