1,C预处理器
C 预处理器不是编译器的组成部分,但是它是编译过程中一个单独的步骤。简言之,C 预处理器只不过是一个文本替换工具而已,它们会指示编译器在实际编译之前完成所需的预处理。我们将把 C 预处理器(C Preprocessor)简写为 CPP。
所有的预处理器命令都是以井号(#)开头。它必须是第一个非空字符,为了增强可读性,预处理器指令应从第一列开始。
#include 导入头文件
#if if判断操作 【if的范畴 必须endif】
#elif else if
#else else
#endif 结束if
#define 定义一个宏
#ifdef 如果定义了这个宏 【if的范畴 必须endif】
#ifndef 如果没有定义这个宏 【if的范畴 必须endif】
#undef 取消宏定义
#pragma 设定编译器的状态
#if 举例:
#if 1 // ifcout << "真" << endl;#elif 0 // else ifcout << "假" << endl;#elsecout << "都不满足" << endl;#endif // 结束ifcout << "结束if" << endl;
#ifdef举例:
#ifndef isRelease // 如果没有isRelease这个宏
#define isRelease 1 // 是否是正式环境下 【我就定义isRelease这个宏】#if isRelease == true
#define RELEASE // 正式环境下 定义RELEASE宏#elif isRelease == false
#define DEBUG // 测试环境下 定义DEBUG宏#endif
#endif
#ifdef DEBUG // 是否定义了DEBUG这个宏cout << "在测试环境" << endl;
#else RELEASEcout << "在正式环境" << endl;
#endif // 结束IF
宏的取消#undef:
#ifdef TEST// 是否定义了这个宏cout << "TEST" << endl;
#undef TEST// 取消宏的定义,下面的代码,就没法用这个宏了,相当于:没有定义过TEST宏
#endif
宏变量:
#define VALUE_I 99
#define VALUE_S "字符串"int main() {int i = VALUE_I; // 预处理阶段 宏会直接完成文本替换工作,替换后的样子:int i = 99;string s = VALUE_S; // 预处理阶段 宏会直接完成文本替换工作,替换后的样子:string s = "字符串";return 0;
}
宏函数,宏函数都是大写
优点:文本替换,不会造成函数调用开销
缺点:会导致代码体积增大
#define ADD(n1, n2) n1 + n2int main() {int r = ADD(1, 2);cout << r << endl;//返回3return 0;
}
2,JNI java与native代码互调
在MainActivity中调用native方法:
public class MainActivity extends AppCompatActivity {public String name = "测试"; // 签名:Ljava/lang/String;static {System.loadLibrary("native-lib"); //静态代码块中加载native-lib文件} public native void changeName();//调用native方法@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);TextView tv = findViewById(R.id.txt_name);changeName();tv.setText(name);}}
使用命令javah -classpath . -jni com.test.MainActivity 生成头文件com_test_MainActivity.h
#include <jni.h>
#include <string>// 解决循环Copy的问题 第二次就进不来了
#ifndef _Included_com_test_MainActivity // 如果没有定义这个宏
#define _Included_com_test_MainActivity // 我就定义这个宏#ifdef __cplusplus // 如果是C++环境
extern "C" { // 全部采用C的方式 不准你函数重载,函数名一样的问题
#endif// 函数的声明
JNIEXPORT jstring JNICALL Java_com_test_MainActivity_changeName(JNIEnv *, jobject);#ifdef __cplusplus // 省略 如果是C++,啥事不干
}#endif
在native-lib.cpp里面实现changeName()方法
#include "com_test_MainActivity.h"// NDK工具链里面的 log 库 引入过来
#include <android/log.h>#define TAG "MainAcitvity"
//自动填充
#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__)// extern "C": 必须采用C的编译方式
// // 无论是C还是C++ 最终是调用到 C的JNINativeInterface,所以必须采用C的方式 extern "C"
// 函数的实现
extern "C"JNIEXPORT // 标记该方法可以被外部调用jstring // Java <---> native 转换用的JNICALL // 代表是 JNI标记// Java_包名_类名_方法名 // JNIEnv * env JNI:的桥梁环境 所有的JNI操作,必须靠他// jobject jobj 谁调用,就是谁的实例 MainActivity this
// jclass clazz 谁调用,就是谁的class MainActivity.classextern "C"
JNIEXPORT void JNICALL
Java_com_test_MainActivity_changeName(JNIEnv *env, jobject thiz) {// 获取classjclass j_cls = env->GetObjectClass(thiz);// 获取属性 L对象类型 都需要L// jfieldID GetFieldID(MainActivity.class, 属性名, 属性的签名)jfieldID j_fid = env->GetFieldID(j_cls, "name", "Ljava/lang/String;");// 转换工作 jstring 为JNI类型jstring j_str = static_cast<jstring>(env->GetObjectField(thiz ,j_fid));// 打印字符串 目标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);// 修改名字 返回给java层jstring jName = env->NewStringUTF("张三");env->SetObjectField(thiz, j_fid, jName);
}
签名规则:
Java的boolean --- Z Java的byte --- BJava的char --- CJava的short --- SJava的int --- IJava的long --- J Java的float --- FJava的double --- DJava的void --- VJava的引用类型 --- Lxxx/xxx/xx/类名;Java的String --- Ljava/lang/String;Java的array int[] --- [I double[][][][] --- [[[Dint add(char c1, char c2) ---- (CC)Ivoid a() === ()V
Java调用native方法,native方法调用java方法,互调
在MainActivity中:
public class MainActivity extends AppCompatActivity {static {System.loadLibrary("native-lib");}public native void callSum();public int sum(int number1, int number2) {return number1 + number2;}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);callSum();}}
在native-lib.cpp中,实现callSum()代码:
extern "C"
JNIEXPORT void JNICALL
Java_com_test_MainActivity_callSum(JNIEnv *env, jobject job) {jclass mainActivityClass = env->GetObjectClass(job);// GetMethodID(MainActivity.class, 方法名, 方法的签名)jmethodID j_mid = env->GetMethodID(mainActivityClass, "sum", "(II)I");// 调用 Java的方法jint sum = env->CallIntMethod(job, j_mid, 8, 8);LOGE("sum result:%d", sum);}
3,JNI 数组操作
MainActivity:
public class MainActivity extends AppCompatActivity {private final static String TAG = MainActivity.class.getSimpleName();static {// System.load(D:/xxx/xxxx/xxx/native-lib); 这种是可以绝对路径的加载动态链接库文件System.loadLibrary("native-lib"); // 这种是从库目录遍历层级目录,去自动的寻找 apk里面的lib/libnative-lib.so}public native void testArrayAction(int count, String textInfo, int[] ints, String[] strs);@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);arrTest();}public void arrTest() {int[] ints = new int[]{1,2,3,4,5,6}; // 基本类型的数组String[] strs = new String[]{"张三","周五","王五"}; // 对象类型的数组testArrayAction(99, "你好", ints, strs);}
}
在native-lib.cpp中:
extern "C"
JNIEXPORT void JNICALL
Java_com_test_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);// 释放jstringenv->ReleaseStringUTFChars(jobj, jobjCharp);}
}
4,JNI对象操作
创建Student类
public class Student {private final static String TAG = Student.class.getSimpleName();public String name;public int age;public String getName() {return name;}public void setName(String name) {Log.d(TAG, "Java setName name:" + name);this.name = name;}public int getAge() {return age;}public void setAge(int age) {Log.d(TAG, "Java setAge age:" + age);this.age = age;}public static void showInfo(String info) {Log.d(TAG, "showInfo info:" + info);}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}
MainActivity:
public class MainActivity extends AppCompatActivity {static {System.loadLibrary("native-lib");}public native void putObject(Student student, String str); // 传递引用类型,传递对象public native void insertObject(); // 凭空创建Java对象public native void testQuote(); // 测试引用public native void delQuote(); // 释放全局引用@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}public void test02(View view) {Student student = new Student(); // Java newstudent.name = "史泰龙";student.age = 88;putObject(student, "九阳神功");}public void test03(View view) {insertObject();}public void test04(View view) {testQuote();}public void test05(View view) {delQuote(); // 必须释放全局引用}@Overrideprotected void onDestroy() {super.onDestroy();delQuote(); // Activity销毁时: 必须释放全局引用}
}
在native-lib.cpp中:
extern "C"
JNIEXPORT void JNICALL
Java_com_test_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/test/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.调用 setNamejstring value = env->NewStringUTF("Zhangsan");env->CallVoidMethod(student, setName, value);// 4.调用 getNamejstring getNameResult = static_cast<jstring>(env->CallObjectMethod(student, getName));const char *getNameValue = env->GetStringUTFChars(getNameResult, NULL);LOGE("调用到getName方法,值是:%s\n", getNameValue);// 5.调用静态showInfojstring jstringValue = env->NewStringUTF("静态方法你好,我是C++");env->CallStaticVoidMethod(studentClass, showInfo, jstringValue);
}
// C++ 堆 栈 ...
// JNI函数 局部引用,全局引用,...
extern "C"
JNIEXPORT void JNICALL
Java_com_test_MainActivity_insertObject(JNIEnv *env, jobject thiz) {/*jstring str = env->GetStringUTFChars();jstring str = env->GetStringUTFChars();jstring str = env->GetStringUTFChars();jstring str = env->GetStringUTFChars();jstring str = env->GetStringUTFChars();jstring str = env->GetStringUTFChars();jstring str = env->GetStringUTFChars();// 好习惯:// 我用完了,我记释放,在我函数执行过程中,不会导致 内存占用多env->ReleaseStringUTFChars()*/// 1.通过包名+类名的方式 拿到 Student class 凭空拿classconst char *studentstr = "com/test/Student";jclass studentClass = env->FindClass(studentstr);// 2.通过student的class 实例化此Student对象 C++ new Studentjobject 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("zhansan");env->CallVoidMethod(studentObj, setName, strValue);env->CallVoidMethod(studentObj, setAge, 99);// env->NewObject() // NewObject 实例化对象,会调用对象的构造函数// ==================== 下面是 Person对象 调用person对象的 setStudent 函数等// 4.通过包名+类名的方式 拿到 Student class 凭空拿classconst char *personstr = "com/test/Person";jclass personClass = env->FindClass(personstr);jobject personObj = env->AllocObject(personClass); // AllocObject 只实例化对象,不会调用对象的构造函数// setStudent 此函数的 签名 规则jmethodID setStudent = env->GetMethodID(personClass, "setStudent","(Lcom/test/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 ... 【函数结束后,会自动释放】
}jclass dogClass; // 你以为这个是全局引用,实际上他还是局部引用extern "C"
JNIEXPORT void JNICALL
Java_com_test_MainActivity_testQuote(JNIEnv *env, jobject thiz) {if (NULL == dogClass) {/*const char * dogStr = "com/derry/as_jni_project/Dog";dogClass = env->FindClass(dogStr);*/// 升级全局引用: JNI函数结束也不释放,反正就是不释放,必须手动释放 ----- 相当于: C++ 对象 new、手动deleteconst char * dogStr = "com/test/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);// 构造函数2init = env->GetMethodID(dogClass, "<init>", "(I)V");dog = env->NewObject(dogClass, init, 100);// 构造函数3init = env->GetMethodID(dogClass, "<init>", "(II)V");dog = env->NewObject(dogClass, init, 200, 300);// 构造函数4init = env->GetMethodID(dogClass, "<init>", "(III)V");dog = env->NewObject(dogClass, init, 400, 500, 600);env->DeleteLocalRef(dog); // 释放// dogClass = NULL; // 是不是问题解决了,不能这样干(JNI函数结束后,还怎么给你释放呢)// 这样就解决了/*env->DeleteGlobalRef(studentClass);studentClass = NULL;*/
}// JNI函数结束,会释放局部引用 dogClass虽然被释放,但是还不等于NULL,只是一个悬空指针而已,所以第二次进不来IF,会崩溃// 非常方便,可以使用了
extern int age; // 声明age
extern void show(); // 声明show函数 5000行代码// 手动释放全局引用
extern "C"
JNIEXPORT void JNICALL
Java_com_test_MainActivity_delQuote(JNIEnv *env, jobject thiz) {if (dogClass != NULL) {LOGE("全局引用释放完毕,上面的按钮已经失去全局引用,再次点击会报错");env->DeleteGlobalRef(dogClass);dogClass = NULL; // 最好给一个NULL,指向NULL的地址,不要去成为悬空指针,为了好判断悬空指针的出现}// 测试下show();
}