1. JNI 的一般开发流程
1.1 定义好本地的 native 方法
1.2 javah 命令生成 .h 头文件
1.3 拷贝 xxx.h、jni_md.h、jni.h 到 VS 的工程目录并添加依赖进来
1.4 实现我们头文件中的 native 方法
1.5 生成 dll 动态,java 引入 dll 动态库运行即可
2.头文件的基础
C语言中#ifdef,#ifndef和#endif的作用
先说结论,这种用法的目的是为了防止重复定义,而不是所谓的重复声明。
其实这一点也是很容易理解的,头文件的定义本身就是为了引入“声明”的,如果不允许重复声明,那多个文件引用同一个头文件就非法了,这显然是错误的,在C编译中,恰恰是允许也需要重复声明的,只要引用声明了,就可以远程使用该声明对应的对象(变量或函数)。
a.h
#include <stdio.h>
#include "b.h"
b.h
#include "a.h"
c.c
#include "a.h"
#include "b.h"
int main(){
printf("Hello!");
}
如果你程序是这样写的话,编译器就会出现Error #include nested too deeply的错误。
因为这里 b.h 和 a.h 都互相包含,c.c文件在include的时候重复include了a.h,我们希望c.c文件中执行#include "b.h"的时候 b.h 能进行判断,如果没有#include "a.h"则include,如果已经include了,则不再重复定义。
可以将b.h修改为:
#ifndef _A_H
#define _A_H
#include "a.h"
#endif
这里,在头文件中出现了变量定义,这种情况下,这个#ifndef才有了作用,第一次被引用时,该定义能够同时被包含,但是第二次,由于已经define xxx了,所以这个定义就不能被重复包含了,这样就能有效的避免重复定义而报错了,因为编译过程中,声明可以,但是重复定义是不可以的。
3. JNIEnv 的实现原理
typedef const structJNINativeInterface_*JNIEnv;
JNIEnv是什么?结构体指针的别名 JNIEnv 其实就是 JNINativeInterface_的指针别名,其实就是一个一级指针了
![](https://img.haomeiwen.com/i16249515/3a5943dda49a40df.png)
实现我们的 native 方法
#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();
}
#include "com_darren_ndk12_NdkSimple.h"
#include "com_darren_ndk12_NdkSimple1.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");
}
JNIEXPORT void JNICALL Java_com_darren_ndk12_NdkSimple1_changeName
(JNIEnv *env, jobject jobj){
// 获取 name 属性然后修改为 Jack
// 3.获取 jclass
jclass j_clz = (*env)->GetObjectClass(env, jobj);
// 获取 jfieldId (JNIEnv *env, jclass clazz, const char *name, const char *sig)
// name 获取哪个属性的属性名
// 2.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 jackName = (*env)->NewStringUTF(env,"Jack");
(*env)->SetObjectField(env, jobj, j_fid, jackName);
}
JNIEXPORT void JNICALL Java_com_darren_ndk12_NdkSimple1_changeAge
(JNIEnv * env, jclass jcls){
// 首先获取原来的
jfieldID j_fid = (*env)->GetStaticFieldID(env,jcls,"age","I");
// Static 获取静态的
jint age = (*env)->GetStaticIntField(env, jcls, j_fid);
// jint -> int
age += 12;
// 设置新的 age 参数
(*env)->SetStaticIntField(env,jcls,j_fid,age);
}
JNIEXPORT void JNICALL Java_com_darren_ndk12_NdkSimple1_callAddMathod
(JNIEnv *env, jobject jobj){
jclass j_clz = (*env)->GetObjectClass(env,jobj);
// 获取 methodid
jmethodID j_mid = (*env)->GetMethodID(env, j_clz, "add", "(II)I");
// 去调用 java 的方法
jint sum = (*env)->CallIntMethod(env, jobj, j_mid,2,3);
printf("sum = %d",sum);
}
public class NdkSimple1 {
public String name = "Darren"; // name = Jack
public static int age = 24;// 24 + 12
public int add(int number1,int number2){
return number1+number2;
}
// 小的思考:静态获取 uuid 的方法,然后再 c 调用这个方法获取uuid
public static String getUUID(){
return UUID.randomUUID().toString();
}
public static void main(String[] args) {
NdkSimple1 ndSimple1 = new NdkSimple1();
/*System.out.println("修改前:"+ndSimple1.name);
ndSimple1.changeName();
System.out.println("修改后:"+ndSimple1.name);*/
/*System.out.println("修改前:"+NdkSimple1.age);
changeAge();
System.out.println("修改后:"+NdkSimple1.age);*/
ndSimple1.callAddMathod();
}
// 改变我们属性
public native void changeName();
public static native void changeAge();
// c 调用 java 方法
public native void callAddMathod();
static{
// 引入加载我们的动态库
// System.loadLibrary :android 加载apk中的libs目录下 .so 库
// System.load : 加载一个具体路径上的 .so 库,去服务器上下载再进行加载(data/data)
System.load("C:/Users/hcDarren/Desktop/android/NDK/NDK_Day12/x64/Debug/NDK_Day12.dll");
}
}
查找方法的规则 javap -v -s 文件名称
网友评论