接续上篇[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++基础①命名空间结构体和引用