1. 预编译器

预处理器不是编译器,预处理器主要完成文本替换的操作,主要完成文本拷贝或者替换工作。预处理器都是用 #xxx 的写法,并不是注释。预处理器主要对#标记的内容进行处理。

1
2
3
4
5
6
7
8
9
10
#include  导入头文件
#if if判断操作 【if的范畴 必须endif】
#elif else if
#else else
#endif 结束if
#define 定义一个宏
#ifdef 如果定义了这个宏 【if的范畴 必须endif】
#ifndef 如果没有定义这个宏 【if的范畴 必须endif】
#undef 取消宏定义
#pragma 设定编译器的状态
1.1 宏函数的优缺点
  • 优点:文本替换,不会造成函数的调用开销(开辟栈空间,形参压栈,函数弹栈释放。
  • 缺点:会导致代码体积增大
1.2 宏函数
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
40
41
42
43
44
45
46
47
48
#include <iostream>
using namespace std;

#define SHOW(V) cout << V << endl; // 参数列表 无需类型 返回值 看不到
#define ADD(n1, n2) n1 + n2
#define CHE(n1, n2) n1 * n2 // 故意制作问题 ,宏函数的注意事项,*可能替换后会优先运算乘积

// 复杂的宏函数
#define LOGIN(V) if(V==1) { \
cout << "满足 你输入的是:" << V << endl; \
} else { \
cout << "不满足 你输入的是:" << V << endl; \
} // 这个是结尾,不需要加 \

void show() {}

int main() {
SHOW(8);
SHOW(8.8f);
SHOW(8.99);

int r = ADD(1, 2);
cout << r << endl;

r = ADD(1+1, 2+2);
cout << r << endl;

// r = CHE(1+1, 2+2);
r = 1+1 * 2+2; // 文本替换:1+1 * 2+2 先算乘法 最终等于 5
cout << r << endl; // 我们认为的是8, 但是打印5

LOGIN(0);
LOGIN(0);
LOGIN(0);
LOGIN(0);
LOGIN(0);
LOGIN(0);
// 会导致代码体积增大

show();
show();
show();
show();
show();
// 普通函数,每次都会进栈 弹栈 ,不会导致代码体积增大

return 0;
}

2. 操作java对象

2.1 函数签名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//签名规则 大写
// MainActivity必须是.class
javap -s -p MainActivity

Java的boolean --- Z 注意点
Java的byte --- B
Java的char --- C
Java的short --- S
Java的int --- I
Java的long --- J 注意点
Java的float --- F
Java的double --- D
Java的void --- V
Java的引用类型 --- Lxxx/xxx/xx/类名;
Java的String --- Ljava/lang/String;
Java的array int[] --- [I double[][][][] --- [[[D
int add(char c1, char c2) ---- (CC)I
void a() === ()V

javap -s -p xxx.class -s 输出xxxx.class的所有属性和方法的签名, -p 忽略私有公开的所有属性方法全部输出
2.2 log工具宏
1
2
3
4
5
6
7
8
// NDK工具链里面的 log 库 引入过来
#include <android/log.h>

#define TAG "JACK"
// ... 我都不知道传入什么 借助JNI里面的宏来自动帮我填充
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
2.3 方法定义说明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// extern "C": 必须采用C的编译方式,为什么,请看JNIEnv内部源码
// // 无论是C还是C++ 最终是调用到 C的JNINativeInterface,所以必须采用C的方式 extern "C"
// 函数的实现
extern "C"

JNIEXPORT // 标记该方法可以被外部调用(VS上不加入 运行会报错, AS上不加入运行没有问题)
// Linux运行不加入,不报错, Win 你必须加入 否则运行报错

jstring // Java <---> native 转换用的

JNICALL // 代表是 JNI标记,可以少

// Java_包名_类名_方法名 ,注意:我们的包名 _ native _1

// JNIEnv * env JNI:的桥梁环境 300多个函数,所以的JNI操作,必须靠他

// jobject jobj 谁调用,就是谁的实例 MainActivity this
// jclass clazz 谁调用,就是谁的class MainActivity.class

// 包名代下划线,声明出来的函数名会以“1 2 3”表示包名中下划线个数
Java_com_jack_as_1jni_1project_MainActivity_getStringPwd
(JNIEnv * env, jobject jobj) {
}
2.4 静态函数定义
1
2
3
4
5
6
// 参数2是jclass
extern "C"
JNIEXPORT jstring JNICALL
Java_com_jack_as_1jni_1project_MainActivity_getStringPwd2(JNIEnv *env, jclass clazz) {
// TODO: implement getStringPwd2()
}
2.5 普通函数
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
// 第二个参数是jobject
extern "C"
JNIEXPORT void JNICALL
Java_com_jcak_as_1jni_1project_MainActivity_changeName(JNIEnv *env, jobject thiz) {
// 获取class
jclass j_cls = env->GetObjectClass(thiz);

// 获取属性 L对象类型 都需要L
// jfieldID GetFieldID(MainActivity.class, 属性名, 属性的签名)
jfieldID j_fid = env->GetFieldID(j_cls, "name", "Ljava/lang/String;");

// 转换工作 jobject转化jstring从父转化成子使用静态转化
jstring j_str = static_cast<jstring>(env->GetObjectField(thiz ,j_fid));

// 打印字符串目标,GetStringUTFChars返回const char *,接收需要常量转化
char * c_str = const_cast<char *>(env->GetStringUTFChars(j_str, NULL));
LOGD("native : %s\n", c_str);
LOGE("native : %s\n", c_str);
LOGI("native : %s\n", c_str);

// 修改成 Beyond
jstring jName = env->NewStringUTF("Beyond");
env->SetObjectField(thiz, j_fid, jName);

// printf() C
// cout << << endl; // C++
}
2.6 修改静态变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
extern "C"
JNIEXPORT void JNICALL
Java_com_jack_as_1jni_1project_MainActivity_changeAge(JNIEnv *env, jclass jcls) {

const char * sig = "I";
//获取静态变量
jfieldID j_fid = env->GetStaticFieldID(jcls, "age", sig);
// 获取值
jint age = env->GetStaticIntField(jcls, j_fid);

age += 10;
// 修改值
env->SetStaticIntField(jcls, j_fid, age);
}
2.7 C调用java方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
extern "C"
JNIEXPORT void JNICALL
Java_com_jack_as_1jni_1project_MainActivity_callAddMethod(JNIEnv *env, jobject job) {
// 自己得到 MainActivity.class
jclass mainActivityClass = env->GetObjectClass(job);

// GetMethodID(MainActivity.class, 方法名, 方法的签名)
jmethodID j_mid = env->GetMethodID(mainActivityClass, "add", "(II)I");

// 调用 Java的方法
jint sum = env->CallIntMethod(job, j_mid, 3, 3);
LOGE("sum result:%d", sum);

}
2.8 操作数组
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// jint == int
// jstring == String
// jintArray == int[]
// jobjectArray == 引用类型对象,例如 String[] Test[] Student[] Person[]
extern "C"
JNIEXPORT void JNICALL
Java_com_jack_as_1jni_1project_MainActivity_testArrayAction(JNIEnv *env, jobject thiz,
jint count,
jstring text_info,
jintArray ints,
jobjectArray strs) {
// ① 基本数据类型 jint count, jstring text_info, 最简单的
int countInt = count; // jint本质是int,所以可以用int接收
LOGI("参数一 countInt:%d\n", countInt);

// const char* GetStringUTFChars(jstring string, jboolean* isCopy)
const char * textInfo = env->GetStringUTFChars(text_info, NULL);
LOGI("参数二 textInfo:%s\n", textInfo);

// ② 把int[] 转成 int*
// jint* GetIntArrayElements(jintArray array, jboolean* isCopy)
int* jintArray = env->GetIntArrayElements(ints, NULL);

// Java层数组的长度
// jsize GetArrayLength(jarray array) // jintArray ints 可以放入到 jarray的参数中去
jsize size = env->GetArrayLength(ints);

for (int i = 0; i < size; ++i) {
*(jintArray+i) += 100; // C++的修改,影响不了Java层
LOGI("参数三 int[]:%d\n", *jintArray+i);
}
// 目前无法控制Java的数组 变化 +100
// 操作杆 ----> JMV
// env->

/**
* 0: 刷新Java数组,并 释放C++层数组
* JNI_COMMIT: 只提交 只刷新Java数组,不释放C++层数组
* JNI_ABORT: 只释放C++层数组
*/
env->ReleaseIntArrayElements(ints, jintArray, 0);

// ③:jobjectArray 代表是Java的引用类型数组,不一样
jsize strssize = env->GetArrayLength(strs);
for (int i = 0; i < strssize; ++i) {

jstring jobj = static_cast<jstring>(env->GetObjectArrayElement(strs, i));

// 模糊:isCopy内部启动的机制
// const char* GetStringUTFChars(jstring string, jboolean* isCopy)
const char * jobjCharp = env->GetStringUTFChars(jobj, NULL);

LOGI("参数四 引用类型String 具体的:%s\n", jobjCharp);

// 释放jstring
env->ReleaseStringUTFChars(jobj, jobjCharp);
}
}
2.9 对象处理
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
// jobject student == Student
// jstring str == String
extern "C"
JNIEXPORT void JNICALL
Java_com_jack_as_1jni_1project_MainActivity_putObject(JNIEnv *env,
jobject thiz,
jobject student,
jstring str) {
const char * strChar = env->GetStringUTFChars(str, NULL);
LOGI("strChar:%s\n", strChar);
env->ReleaseStringUTFChars(str, strChar);

// --------------
// 1.寻找类 Student
// jclass studentClass = env->FindClass("com/jack/as_jni_project/Student"); // 第一种
jclass studentClass = env->GetObjectClass(student); // 第二种

// 2.Student类里面的函数规则 签名
jmethodID setName = env->GetMethodID(studentClass, "setName", "(Ljava/lang/String;)V");
jmethodID getName = env->GetMethodID(studentClass, "getName", "()Ljava/lang/String;");
jmethodID showInfo = env->GetStaticMethodID(studentClass, "showInfo", "(Ljava/lang/String;)V");

// 3.调用 setName
jstring value = env->NewStringUTF("AAAA");
env->CallVoidMethod(student, setName, value);

// 4.调用 getName
jstring getNameResult = static_cast<jstring>(env->CallObjectMethod(student, getName));
const char * getNameValue = env->GetStringUTFChars(getNameResult, NULL);
LOGE("调用到getName方法,值是:%s\n", getNameValue);

// 5.调用静态showInfo
jstring jstringValue = env->NewStringUTF("静态方法你好,我是C++");
env->CallStaticVoidMethod(studentClass, showInfo, jstringValue);
}
2.10 对象处理进阶
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
40
41
42
43
44
45
46
47
48
extern "C"
JNIEXPORT void JNICALL
Java_com_jack_as_1jni_1project_MainActivity_insertObject(JNIEnv *env, jobject thiz) {
// 1.通过包名+类名的方式 拿到 Student class 凭空拿class
const char *studentstr = "com/jack/as_jni_project/Student";
jclass studentClass = env->FindClass(studentstr);

// 2.通过student的class 实例化此Student对象 C++ new Student
jobject studentObj = env->AllocObject(studentClass); // AllocObject 只实例化对象,不会调用对象的构造函数

// 方法签名的规则
jmethodID setName = env->GetMethodID(studentClass, "setName", "(Ljava/lang/String;)V");
jmethodID setAge = env->GetMethodID(studentClass, "setAge", "(I)V");

// 调用方法
jstring strValue = env->NewStringUTF("jack");
env->CallVoidMethod(studentObj, setName, strValue);
env->CallVoidMethod(studentObj, setAge, 99);


// env->NewObject() // NewObject 实例化对象,会调用对象的构造函数


// ==================== 下面是 Person对象 调用person对象的 setStudent 函数等

// 4.通过包名+类名的方式 拿到 Student class 凭空拿class
const char *personstr = "com/jack/as_jni_project/Person";
jclass personClass = env->FindClass(personstr);

jobject personObj = env->AllocObject(personClass); // AllocObject 只实例化对象,不会调用对象的构造函数

// setStudent 此函数的 签名 规则
jmethodID setStudent = env->GetMethodID(personClass, "setStudent",
"(Lcom/jack/as_jni_project/Student;)V");

env->CallVoidMethod(personObj, setStudent, studentObj);

// 第一类
env->DeleteLocalRef(studentClass);
env->DeleteLocalRef(personClass);
env->DeleteLocalRef(studentObj);
env->DeleteLocalRef(personObj);

// 第二类
// env->ReleaseStringUTFChars()

// TODO 局部引用: jobject jclass jstring ... 【函数结束后,会自动释放】
}
2.11 全局引用与局部引用理解

使用NewGlobalRef创建全局引用,否者都是局部引用,出栈之后,dogclass会成为悬空指针。

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
jclass dogClass; // 你以为这个是全局引用,实际上他还是局部引用

extern "C"
JNIEXPORT void JNICALL
Java_com_jack_as_1jni_1project_MainActivity_testQuote(JNIEnv *env, jobject thiz) {
if (NULL == dogClass) {
/*const char * dogStr = "com/jack/as_jni_project/Dog";
dogClass = env->FindClass(dogStr);*/

// 升级全局引用: JNI函数结束也不释放,反正就是不释放,必须手动释放 ----- 相当于: C++ 对象 new、手动delete
const char * dogStr = "com/jack/as_jni_project/Dog";
jclass temp = env->FindClass(dogStr);
dogClass = static_cast<jclass>(env->NewGlobalRef(temp)); // 提升全局引用
// 记住:用完了,如果不用了,马上释放
env->DeleteLocalRef(temp);
}

// <init> V 是构造函数名不会变的

// 构造函数一
jmethodID init = env->GetMethodID(dogClass, "<init>", "()V");
jobject dog = env->NewObject(dogClass, init);

// 构造函数2
init = env->GetMethodID(dogClass, "<init>", "(I)V");
dog = env->NewObject(dogClass, init, 100);


// 构造函数3
init = env->GetMethodID(dogClass, "<init>", "(II)V");
dog = env->NewObject(dogClass, init, 200, 300);

// 构造函数4
init = env->GetMethodID(dogClass, "<init>", "(III)V");
dog = env->NewObject(dogClass, init, 400, 500, 600);

env->DeleteLocalRef(dog); // 释放
}

// JNI函数结束,会释放局部引用 dogClass虽然被释放,但是还不等于NULL,只是一个悬空指针而已,所以第二次进不来IF,会奔溃

// 非常方便,可以使用了
extern int age; // 声明age
extern void show(); // 声明show函数

// 手动释放全局引用
extern "C"
JNIEXPORT void JNICALL
Java_com_jack_as_1jni_1project_MainActivity_delQuote(JNIEnv *env, jobject thiz) {
if (dogClass != NULL) {
LOGE("全局引用释放完毕,上面的按钮已经失去全局引用,再次点击会报错");
env->DeleteGlobalRef(dogClass);
dogClass = NULL; // 最好给一个NULL,指向NULL的地址,不要去成为悬空指针
}

// 测试下
show();
}