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 // if
cout << "真" << endl;
#elif 0 // else if
cout << "假" << endl;
#else
cout << "都不满足" << endl;
#endif // 结束if
cout << "结束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 RELEASE
cout << "在正式环境" << 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 + n2
int main() {
int r = ADD(1, 2);
cout << r << endl;//返回3
return 0;
}
在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方法
@Override
protected 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
#include
// 解决循环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
#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.class
extern "C"
JNIEXPORT void JNICALL
Java_com_test_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;");
// 转换工作 jstring 为JNI类型
jstring j_str = static_cast(env->GetObjectField(thiz ,j_fid));
// 打印字符串 目标
char * c_str = const_cast(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 --- 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
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;
}
@Override
protected 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);
}
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);
@Override
protected 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(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);
}
}
创建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);
}
@Override
public 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(); // 释放全局引用
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void test02(View view) {
Student student = new Student(); // Java new
student.name = "史泰龙";
student.age = 88;
putObject(student, "九阳神功");
}
public void test03(View view) {
insertObject();
}
public void test04(View view) {
testQuote();
}
public void test05(View view) {
delQuote(); // 必须释放全局引用
}
@Override
protected 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.调用 setName
jstring value = env->NewStringUTF("Zhangsan");
env->CallVoidMethod(student, setName, value);
// 4.调用 getName
jstring getNameResult = static_cast(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);
}
// 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 凭空拿class
const char *studentstr = "com/test/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("zhansan");
env->CallVoidMethod(studentObj, setName, strValue);
env->CallVoidMethod(studentObj, setAge, 99);
// env->NewObject() // NewObject 实例化对象,会调用对象的构造函数
// ==================== 下面是 Person对象 调用person对象的 setStudent 函数等
// 4.通过包名+类名的方式 拿到 Student class 凭空拿class
const 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、手动delete
const char * dogStr = "com/test/Dog";
jclass temp = env->FindClass(dogStr);
dogClass = static_cast(env->NewGlobalRef(temp)); // 提升全局引用
// 记住:用完了,如果不用了,马上释放
env->DeleteLocalRef(temp);
}
// V 是不会变的
// 构造函数一
jmethodID init = env->GetMethodID(dogClass, "", "()V");
jobject dog = env->NewObject(dogClass, init);
// 构造函数2
init = env->GetMethodID(dogClass, "", "(I)V");
dog = env->NewObject(dogClass, init, 100);
// 构造函数3
init = env->GetMethodID(dogClass, "", "(II)V");
dog = env->NewObject(dogClass, init, 200, 300);
// 构造函数4
init = env->GetMethodID(dogClass, "", "(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();
}