jni基础

JNI 的一般开发流程

1 定义好本地的 native 方法
2 javah 命令生成 .h 头文件
3 拷贝 xxx.h、jni_md.h、jni.h 到 VS 的工程目录并添加依赖进来
4 实现我们头文件中的 native 方法
5 生成 dll 动态,java 引入 dll 动态库运行即可

生成的 .h 文件参数详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"// "" 引入自己工程的头文件 <> 引入系统的头文件
/* Header for class com_darren_ndk12_NdkSimple */
// 用来打一个标记,c在编译的时候会把头文件 copy 到你引入的地方,不管是重复引用还是相互引用都只会 copy 一次
#ifndef _Included_com_darren_ndk12_NdkSimple
#define _Included_com_darren_ndk12_NdkSimple
#ifdef __cplusplus // 相当于 if 语句 c++
// 不管是 c 还是 c++ 统一都是采用 c 的编译方式,因为在c里面是不允许函数重载的,但是在 c++ 里面可以
extern "C" {
#endif
/*
* Class: com_darren_ndk12_NdkSimple
* Method: getSingnaturePassword
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_darren_ndk12_NdkSimple_getSingnaturePassword
(JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

实现类详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 实现我们的 native 方法
#include "com_darren_ndk12_NdkSimple.h"

// JNIEXPORT JNI 一个关键字,不能少(编译能通过),标记为该方法可以被外部调用
// jstring : 代表 java 中的 String
// JNICALL: 也是一个关键字,可以少的 jni call
// JNIEnv: 这个是 c 和 java 相互调用的桥梁,所有 function 搞清
// jobject: java传递下来的对象,就是本项目中 JniSimple java 对象
// jclass: 静态函数的时候java传递下来的 class 对象,就是本项目中的 JniSimple.class
JNIEXPORT jstring JNICALL Java_com_darren_ndk12_NdkSimple_getSingnaturePassword
(JNIEnv * env, jobject jobj){
// JNIEnv * 其实已经是一个二级指针了,所以 -> 调用的情况下必须是一级指针 *取值
return (*env)->NewStringUTF(env,"940223");
}

JNIEnv是什么

jni.h里面有如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
* JNI Native Method Interface.
*/

struct JNINativeInterface_;

struct JNIEnv_;


#ifdef __cplusplus //如果是c++就是JNIEnv_对象 JNIEnv_对象里面其实也是通过JNINativeInterface_调用
typedef JNIEnv_ JNIEnv;
#else //如果是c的话就是JNINativeInterface_指针
typedef const struct JNINativeInterface_ *JNIEnv;
#endif



struct JNIEnv_ {
const struct JNINativeInterface_ *functions;
// ....
}

在实现的函数里面

1
2
3
4
5
JNIEXPORT jstring JNICALL Java_com_darren_ndk12_NdkSimple_getSingnaturePassword
(JNIEnv * env, jobject jobj){
// JNIEnv * 其实已经是一个二级指针了,所以 -> 调用的情况下必须是一级指针 *取值
return (*env)->NewStringUTF(env,"940223");
}

所以为什么在c里面调用函数需要用(*env)-> ,而c++只需要用env->

JNIEnv流程

手写JNIEnv流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <stdlib.h>
#include <stdio.h>
// 定义一个结构体指针的别名
typedef const struct JNINativeInterface *JNIEnv;
// 模拟一个结构体
struct JNINativeInterface{
// 结构体的方法指针
char*(*NewStringUTF)(JNIEnv*,char*);
};

char* NewStringUTF(JNIEnv* env, char* c_str){
// c_str -> jstring
return c_str;
}

char* Java_com_darren_getSingnaturePassword(JNIEnv * env){
// JNIEnv * 其实已经是一个二级指针了,所以 -> 调用的情况下必须是一级指针 *取值
return (*env)->NewStringUTF(env, "940223");
}

void main(){

// 构建 JNIEnv* 对象
struct JNINativeInterface nativeInterface;
// 给结构方法指针进行复制(实现)
nativeInterface.NewStringUTF = NewStringUTF;

// 传给 Java_com_darren_ndk12_NdkSimple_getSingnaturePassword 的参数是 JNIEnv*
JNIEnv env = &nativeInterface;// 一级指针
JNIEnv* jniEnv = &env;// 二级指针

// 把 jniEnv 对象传给 Java_com_darren_ndk12_NdkSimple_getSingnaturePassword
char* jstring = Java_com_darren_getSingnaturePassword(jniEnv);

// jstring 通过 JNIEnv 传给 java 层
printf("jstring = %s",jstring);

getchar();
}

-------------The End-------------