JNI:01.JNIEnv 的实现原理

代码请看:07.JNI基础-JNI Env 实现原理

-> 调用的情况下必须是一级指针 *取值

  • env->GetXXFieldID:获取字段的ID
  • env->GetXXField:获取字段上的值,返回XX类型
  • env->GetMethodID:获取方法的ID
  • env->CallXXMethod:调用方法。返回XX类型

1. JNI 的一般开发流程:

  1. 定义好本地的 native 方法,生成.class文件。
  2. 在存放原文件 .java的包名文件夹的最前面(src文件夹下)使用javah 命令生成 .h 头文件 javah -d jni -jni com.east.NdkSample
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    用法:
    javah [options] <classes>
    其中, [options] 包括:
    -o <file> 输出文件 (只能使用 -d 或 -o 之一)
    -d <dir> 输出目录
    -v -verbose 启用详细输出
    -h --help -? 输出此消息
    -version 输出版本信息
    -jni 生成 JNI 样式的标头文件 (默认值)
    -force 始终写入输出文件
    -classpath <path> 从中加载类的路径
    -cp <path> 从中加载类的路径
    -bootclasspath <path> 从中加载引导类的路径
    <classes> 是使用其全限定名称指定的
    (例如, java.lang.Object)。
  3. 拷贝 xxx.hjni_md.hjni.h 到 VS 的工程目录并添加依赖进来。(jni_md.h和jni.h在java安装目录下搜索即可查找到)
  4. 实现我们头文件中的 native 方法
  5. VS生成解决方案即可生成 .dll 动态库,java 引入 dll 动态库运行即可

java 中加载动态库:

  • System.loadLibrary :android 加载apk中的libs目录下 .so 库
  • System.load : 加载一个具体路径上的 .so 库,去服务器上下载再进行加载(data/data)

2. 详解 .h 文件和实现文件

com_east_NdkSample.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "jni.h"
/* Header for class com_east_NdkSample */
// 用来打一个标记, c 在编译的时候会把头文件 copy 到你引入的地方,不管是重复引用还是相互引用都只会 copy 一次
#ifndef _Included_com_east_jni07_NdkSample
#define _Included_com_east_jni07_NdkSample
#ifdef __cplusplus // 相当于 if 语句 c++
// 不管是 c 还是 c++ 统一都是采用 c 的编译方法,因为在 c 里面是不允许函数重载的,但是在 c++ 里面可以
extern "C" {
#endif
/*
* Class: com_east_jni07_NdkSample
* Method: getSingnaturePassword
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_east_jni07_NdkSample_getSingnaturePassword
(JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

Simple.c:

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

// JNIEXPORT: JNI 一个关键字,不能少(编译能通过),标记为该方法可以被外部调用
// jstring:代表 java 中的 String
// JNICALL:也是一个关键字,可以少的 jni call
// jobject: 非静态方法时 java 传递下来的类对象,就是本项目中 NdkSample java 对象
// jclass:静态方法时,java 传递下来的 class 对象,就是本项目中的 NdkSample.class
JNIEXPORT jstring JNICALL Java_com_east_NdkSample_getSingnaturePassword
(JNIEnv * env, jclass clz){
// C 中 typedef const struct JNINativeInterface* JNIEnv; 已经是一个结构体指针了
// C++ 中 typedef _JNIEnv JNIEnv; // 就是一个结构体
// JNIEnv * 其实已经是一个二级指针了,所以 -> 调用的情况下必须是一级指针 *取值
return (*env)->NewStringUTF(env, "eastrise");
}

3. JNIEnv 的实现原理

1
2
3
4
5
6
7
#if defined(__cplusplus) // 如果是C++文件 
typedef _JNIEnv JNIEnv; JNIEnv 就是结构体 _JNIEnv
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv; // 如果是C文件 JNIEnv 是JNINativeInterface的一级指针
typedef const struct JNIInvokeInterface* JavaVM;
#endif

看下 JNINativeInterface 的部分代码:

1
2
3
4
5
6
7
8
9
struct JNINativeInterface {
void* reserved0;
void* reserved1;
void* reserved2;
void* reserved3;

jint (*GetVersion)(JNIEnv *); // 函数指针
....
}

看下 _JNIEnv 的部分代码(调用的还是 JNINativeInterface 的方法):

1
2
3
4
5
6
7
8
9
10
11
struct _JNIEnv {
/* do not rename this; it does not seem to be entirely opaque */
const struct JNINativeInterface* functions;

#if defined(__cplusplus) // 如果是 C++ ,调用的还是 JNINativeInterface 的方法

jint GetVersion()
{ return functions->GetVersion(this); }
...
#endif
}

4. C 中访问和修改 java 的属性和方法

  • env->GetXXFieldID:获取字段的ID
  • env->GetXXField:获取字段上的值,返回XX类型

进入类编译后的.class文件,全包名路径的目录下。

获取类中:属性和方法签名:javap -p -s com.east.jni07.NdkSample

  • -s:输出内部类型签名
  • -p:显示所有类和成员

4.1 C中修改 java 的普通属性

java 中的属性:public String name = "Eastrise";

java 中的本地方法:public native void changeName();

c中的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
JNIEXPORT void JNICALL Java_com_east_NdkSample1_changeName
(JNIEnv * env, jobject jobj){

// 1. 获取 jclass
jclass j_clz = (*env)->GetObjectClass(env,jobj);
// 2. 获取 jfieldID
// 获取 jfieldId (JNIEnv *env, jclass clazz, const char *name, const char *sig)
// name 获取哪个属性的属性名 ,sig 属性的签名
jfieldID j_fid = (*env)->GetFieldID(env, j_clz, "name", "Ljava/lang/String;");
// 1. 获取 name 的属性值
jstring j_str = (*env)->GetObjectField(env,jobj,j_fid);

// 打印字符串 jstring -> c_str
char* c_str = (*env)->GetStringUTFChars(env, j_str, NULL);

printf("name is %s", c_str);

// 修改成 Jack
jstring jack_name = (*env)->NewStringUTF(env,"Jack");
(*env)->SetObjectField(env,jobj,j_fid,jack_name);
}

4.2 C中修改 java 的静态属性

java 中的属性:public static int age = 24;

java 中的本地静态方法:public static native void changeAge();

c中的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 静态方法参数会变为 jclass 代表 NdkSample1.class
JNIEXPORT void JNICALL Java_com_east_NdkSample1_changeAge
(JNIEnv * env, jclass jcls){

// 2. 获取 jfieldID
jfieldID j_fid = (*env)->GetStaticFieldID(env,jcls,"age","I");
// 1. 获取 age 的值 Static 获取静态的
jint age = (*env)->GetStaticIntField(env,jcls,j_fid);

// jnit == int
age += 12;

(*env)->SetStaticIntField(env,jcls,j_fid,age);
}

4.3 c 调用 java 方法

  • env->GetMethodID:获取方法的ID
  • env->CallXXMethod:调用方法。返回XX类型

java 中的方法:

1
2
3
public int add(int number1,int number2){
return number1+number2;
}

c中的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// c 调用 java 层普通方法
JNIEXPORT void JNICALL Java_com_east_NdkSample1_callAddMethod
(JNIEnv * env, jobject jobj){

// 3. 获取 jclass
jclass jcls = (*env)->GetObjectClass(env,jobj);

// 2. 获取 jmethodID
jmethodID j_mid = (*env)->GetMethodID(env,jcls,"add","(II)I");

// 1. 调用 java 方法
jint sum = (*env)->CallObjectMethod(env,jobj,j_mid,2,3);

printf("sum = %d",sum);
}
-------------本文结束感谢您的阅读-------------
坚持原创技术分享,您的支持将鼓励我继续创作!
0%