接續上篇[JNI開發系列①JNI概念及開發流程](http://www.jianshu.com/p/68bca86a84ce)
### 前情提要
JNI技術 , 是java世界與C/C++世界的通信基礎 , java語言可以通過native方法去調用C/C++的函數 , 也可以通過C/C++來調用java的字段與方法 。 在上篇中 , 我們了解了JNI開發的基本流程 , 接下來我們來分析分析C語言代碼以及頭文件 。
### .h頭文件分析
> 頭文件生成命令 : javah com.zeno.jni.HelloJni
```java
public static native String getStringFromC() ;
```
上述代碼 通過`javah`命令 , 則會生成如下頭文件中的函數:
```c
/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"
/* Header for class com_zeno_jni_HelloJni */
#ifndef _Included_com_zeno_jni_HelloJni
#define _Included_com_zeno_jni_HelloJni
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_zeno_jni_HelloJni
* Method: getStringFormC
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_zeno_jni_HelloJni_getStringFromC
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
```
通過上述代碼, 我們可以看出`getStringFromC()`方法 , 生成的函數是`Java_com_zeno_jni_HelloJni_getStringFromC (JNIEnv *, jclass)`函數 。其實 ,我們不用`javah`命令 , 也能寫出頭文件 , 除了`#endif,#ifdef __cplusplus`中間是變化的 , 其他的都不變 , `javah`通過`native`方法生成的函數 , 命名都是有規律 。
```
函數名稱規則:Java_完整類名_方法名 , 包名的.號 , 以`_`表示
```
> 其中jstring是返回的java的String類型 , jstring類型是jni里面定義的類型 , 標準C里面是沒有的 。那么 , jstring是什么類型呢 ?
使用VS的轉到定義功能 , 我們可以看到 , jstring在jni.h的定義 , jstring是jobject的別名 , jobject是一個_jobject結構體的指針 。
```c
typedef jobject jstring;
typedef struct _jobject *jobject;
```
因為我們`getStringFromC()`方法返回的是一個String類型 , 所以C函數的返回值是`jstring`類型 。
```c
/*
* 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");
}
```
在C語言系列的最后一篇 , 我們分析了jni.h的頭文件 , 了解了JNIEnv結構體指針 , 大致知道里面都有些什么函數 , `NewStringUTF(Env, "Jni C String")`這個函數 , 就是將C語言中的字符指針轉換成java的`String`類型的字符串。
### JNI數據類型對應java的標準數據類型
|Java Type|Native Type|Description|
|:----:|:----:|:----:|
|boolean|jboolean|unsigned 8 bits|
|byte|jbyte|signed 8 bits|
|char|jchar|unsigned 16 bits|
|short|jshort|signed 16 bits|
|int|jint|signed 32 bits|
|long|jlong|signed 64 bits|
|float|jfloat|32 bits|
|double|jdouble|64 bits|
|void|void|not applicable|
### JNI數據類型對應java的引用數據類型
```c
struct _jobject;
typedef struct _jobject *jobject;
typedef jobject jclass;
typedef jobject jthrowable;
typedef jobject jstring;
typedef jobject jarray;
typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
typedef jarray jobjectArray;
```
在jni源碼中 , 我們可以看到如上定義 , 可以發現 , 所有的引用類型都是`_jobject `結構體指針類型。
### JNIEnv分析
我們知道 ,JNIEnv是`JNINativeInterface_`結構體的指針別名 , 在`JNINativeInterface_`結構體中 , 定義很多操作函數 。例如:
```c
jstring (JNICALL *NewStringUTF) (JNIEnv *env, const char *utf);
jsize (JNICALL *GetStringUTFLength) (JNIEnv *env, jstring str);
const char* (JNICALL *GetStringUTFChars)(JNIEnv *env, jstring str, jboolean *isCopy);
void (JNICALL *ReleaseStringUTFChars)(JNIEnv *env, jstring str, const char* chars);
```
由上述函數可以看出,每個函數都需要一個JNIEnv指針,但是為什么需要呢 ?
> 有兩點:
第一:函數需要 , 在函數中仍然需要JNINativeInterface_結構體中的函數做處理
第二:區別對待C和C++
我們知道 , jni是支持C/C++的,在jni.h頭文件中 , 那么C++是怎么表示JNIEnv的呢 ?源碼如下:
```c++
struct JNIEnv_ {
const struct JNINativeInterface_ *functions;
#ifdef __cplusplus
jint GetVersion() {
return functions->GetVersion(this);
}
jclass DefineClass(const char *name, jobject loader, const jbyte *buf,
jsize len) {
return functions->DefineClass(this, name, loader, buf, len);
}
jclass FindClass(const char *name) {
return functions->FindClass(this, name);
}
jmethodID FromReflectedMethod(jobject method) {
return functions->FromReflectedMethod(this,method);
}
```
在C++環境下 ,還是使用的`NINativeInterface_`結構體指針調用函數, 使用`NewStringUTF`函數時, 則不需要傳入`Env`這個二級指針 ,因為C++是面向對象的語言 , 傳入了this , 當前環境的指針
```c++
jstring NewStringUTF(const char *utf) {
return functions->NewStringUTF(this,utf);
}
```
示例:
```c++
/*
* Class: com_zeno_jni_HelloJni
* Method: getStringFormC
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_zeno_jni_HelloJni_getStringFromCPP
(JNIEnv * env, jclass jclazz) {
return env->NewStringUTF("From C++ String");
}
```
在C++環境中 , JNIEnv就成了一級指針了 , 為什么會是這樣呢 ?我們在源碼中找到這樣一段代碼:
```c
/*
* JNI Native Method Interface.
*/
struct JNINativeInterface_;
struct JNIEnv_;
#ifdef __cplusplus
typedef JNIEnv_ JNIEnv;
#else
typedef const struct JNINativeInterface_ *JNIEnv;
#endif
```
由上可知 , 在C和C++兩個環境中 , 使用了兩個不同的JNIEnv , 一個是JNIEnv二級指針 , 一個是JNIEnv一級指針 。
### 模擬C語言中的JNIEnv寫法
```c
#include <stdio.h>
#include <stdlib.h>
// 定義一個JNIEnv , 是JavaNativeInterface結構體指針的別名
typedef struct JavaNativeInterface* JNIEnv;
// 模擬java本地化接口結構體
struct JavaNativeInterface {
void(*hadlefunc)(JNIEnv*);
char*(*NewStringUTF)(JNIEnv*, char*);
};
// 模擬 , 在調用NewStringUTF函數的時候 , 需要處理一些事情
void handleFunction(JNIEnv* env) {
printf("正在處理...\n");
}
// 最終調用的函數實現
char* NewStringUTF(JNIEnv* env,char* utf) {
(*env)->hadlefunc(env);
return utf;
}
void main() {
//實例化結構體
struct JavaNativeInterface jnf;
jnf.hadlefunc = handleFunction;
jnf.NewStringUTF = NewStringUTF;
// 結構體指針
JNIEnv e = &jnf;
// 二級指針
JNIEnv* env = &e;
// 通過二級指針掉用函數
char* res = (*env)->NewStringUTF(env, "模擬JNIEnv實現方式\n");
// 打印
printf("調用結果:%s", res);
system("pause");
}
```
輸出:
```c
正在處理...
調用結果:模擬JNIEnv實現方式
```
###結語
.h頭文件的分析就到這里 ,關鍵是了解清楚 , `native`方法在C中生成函數名稱的規則 , 以及對JNIEnv有個良好的認識 。
- 簡介
- C語言基礎及指針①語法基礎
- C語言基礎及指針②之指針內存分析
- C語言基礎及指針③函數與二級指針
- C語言基礎及指針④函數指針
- C語言基礎及指針⑤動態內存分配
- C語言基礎及指針⑥字符操作
- C語言基礎及指針⑦結構體與指針
- C語言基礎及指針⑧文件IO
- C語言基礎及指針⑨聯合體與枚舉
- C語言基礎及指針⑩預編譯及jni.h分析
- JNI開發系列①JNI概念及開發流程
- JNI開發系列②.h頭文件分析
- JNI開發系列③C語言調用Java字段與方法
- JNI開發系列④C語言調用構造方法
- JNI開發系列⑤對象引用的處理
- NDK開發基礎①使用Android Studio編寫NDK
- NDK開發基礎②文件加密解密與分割合并
- NDK開發基礎③增量更新之服務器端生成差分包
- C++基礎①命名空間結構體和引用