JNI 基礎 - JNIEnv 的實現原理

一.JNI 開發的通常流程

在 windows 系統上面咱們常常能看到不少相似於 xxx.dll 的文件,在作 android 開發的時候咱們能看到不少 xxx.so 的文件。這些都是啥呢?其實就是用 c 和 c++ 實現生成的動態庫,供 windows 和 android 系統來調用。java

咱們解壓 QQ 和支付寶的 apk 找到它的 libs 目錄下,會發現有大量的 .so 庫文件,還有不少是動態下載加載的咱們看不到,能看到的就已經有好幾百個了。爲何要這麼弄?直接就用 java 開發不行嗎?其實好處有不少,好比安全,高效,跨平臺等等。今天咱們就來看下 JNI 開發的通常開發流程。android

1.1 編寫 native 方法c++

public class NdkTest {
	public static void main(String[] args) {
		NdkTest ndkTest = new NdkTest();
		
		System.out.println("簽名密鑰:"+ndkTest.getSignaturePassword());
	}
	
	// 獲取簽名密鑰
	public native String getSignaturePassword();
	
	static{
		// 加載某個路徑下的動態庫
		System.load("C:/Users/hcDarren/Desktop/android/NDK/NDK_Day12/x64/Debug/NDK_Day12.dll");
	}
}
複製代碼

1.2 生成 xxx.h 頭文件編程

javah -d ../jni -jni com.darren.ndk12.NdkTest
複製代碼

1.3 VS 編寫實現方法生成 dll 動態庫windows

// 引入頭文件
#include "com_darren_ndk12_NdkTest.h"

JNIEXPORT jstring JNICALL Java_com_darren_ndk12_NdkTest_getSignaturePassword
(JNIEnv *env, jobject jobj){
	return (*env)->NewStringUTF(env,"940223");
}
複製代碼

二.詳解 .h 頭文件和實現文件

/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h" // 引入頭 jni.h 文件
/* Header for class com_darren_ndk12_NdkTest */
// 打個標記,防止反覆引入 copy 內容
#ifndef _Included_com_darren_ndk12_NdkTest
#define _Included_com_darren_ndk12_NdkTest
#ifdef __cplusplus 
// 若是是 c++ 則統一用 C 的編譯方式
// 會指示編譯器這部分代碼按C語言的進行編譯,而不是C++的。
// C語言並不支持函數重載,所以編譯C語言代碼的函數時不會帶上函數的參數類型,通常只包括函數名。
extern "C" { 
#endif
/*
 * Class:     com_darren_ndk12_NdkTest
 * Method:    getSignaturePassword
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_darren_ndk12_NdkTest_getSignaturePassword
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif
複製代碼
// 引入頭文件
#include "com_darren_ndk12_NdkTest.h"
// JNIEXPORT:在Jni編程中全部本地語言實現Jni接口的一個標誌
// jstring:對應 java 中的數據類型 String
// JNICALL:也是一個標記能夠去掉,編譯運行也不會有問題
// JNIEnv:c 與 java 相互調用的橋樑,它提供了不少函數方法
// jobj:java 傳遞下來的對象,即上面的 NdkTest
JNIEXPORT jstring JNICALL Java_com_darren_ndk12_NdkTest_getSignaturePassword
(JNIEnv *env, jobject jobj){
        //  經過 JNIEnv 把 c 字符串轉爲 jstring
	return (*env)->NewStringUTF(env,"940223");
}
複製代碼

####三.JNIEnv 的實現原理 JNI 的基礎學習咱們主要搞清 JNIEnv 就能夠了,只要熟悉它裏面的函數方法。可是學習的時候確定不光要搞清它的方法函數,還須要搞清它的實現原理。若是有留意我以前寫的一些文章你會發現 c 和 c++ 有所不一樣,c++ 是這樣的。安全

JNIEXPORT jstring JNICALL Java_com_darren_ndk12_NdkTest_getSignaturePassword
(JNIEnv *env, jobject jobj){
	return env -> NewStringUTF("940223");
}
複製代碼

爲何會有這樣的不一樣?c++ 的實現方式咱們後面的文章再去詳細講解,咱們先來模擬一下 c 的實現方式:bash

// 引入頭文件
#include <stdio.h>

// 定義結構體指針別名
typedef const struct JNINativeInterface *JNIEnv;

// 定義一個結構體
struct JNINativeInterface{
	// 定義的不是函數,函數指針
	char*(*NewStringUTF)(JNIEnv*, char*);
};

// 只是作一個模擬
char* Java_com_darren_ndk12_NdkTest_getSignaturePassword(JNIEnv *env){
	return (*env)->NewStringUTF(env, "940223");
}

// NewStringUTF 方法的實現
char* NewStringUTF(JNIEnv* jniEnv, char* str){
	// 一連串的實現 char* -> jstring
	return str;
}

void main(){
	// 中間還會有不少的流程,咱們能看到的就是調用 Java_com_darren_ndk12_NdkTest_getSignaturePassword
	// 模擬 JNIEnv 建立過程
	struct JNINativeInterface nativeInterface;
	nativeInterface.NewStringUTF = NewStringUTF;
	JNIEnv jniEnv = &nativeInterface;// 一級指針
	// Java_com_darren_ndk12_NdkTest_getSignaturePassword 參數須要的是 JNIEnv*
	JNIEnv* env = &jniEnv;// 雖然只有一個 * ,可是其實他是一個二級指針

	char* singnature = Java_com_darren_ndk12_NdkTest_getSignaturePassword(env);
	printf("singnature = %s", singnature);
	// 而後將 jstring 返回給 java

	getchar();
}
複製代碼

視頻連接:https://pan.baidu.com/s/1vyxCSn0SWo3-YnoD7Rzryw 視頻密碼:uqmc函數

相關文章
相關標籤/搜索