一、宏
C++ 宏定義將一個標識符定義為一個字符串,源程序中的該標識符均以指定的字符串來代替,比如定義常量。
C++編譯的四大過程:預處理,預編譯,匯編,鏈接;
預處理:預處理階段主要處理include和define等。它把#include包含進來的.h 文件插入到#include所在的位置,把源程序中使用到的用#define定義的宏用實際的字符串代替,宏定義的展開,宏定義的替換。
預處理器不是編譯器,預處理器主要完成文本替換的操作,預處理器都是用 #xxx 的寫法。
#include 導入頭文件
#if if判斷操作 【if的范疇 必須endif】
#elif else if
#else else
#endif 結束if
#define 定義一個宏
#ifdef 如果定義了這個宏 【if的范疇 必須endif】
#ifndef 如果沒有定義這個宏 【if的范疇 必須endif】
#undef 取消宏定義
#pragma 設定編譯器的狀態(tài)
1、宏定義示例
//宏定義示例:
#ifndef CLIONCPPPROJECT_T2_H // 如果沒有定義這個宏 解決循環(huán)拷貝的問題
#define CLIONCPPPROJECT_T2_H // 我就定義這個宏
// 100 行代碼
// 第一次能夠進來
// 第二次 第n此進不來 直接 解決循環(huán)拷貝的問題了
// ---------------
#ifndef isRelease // 如果沒有isRelease這個宏
#define isRelease 1 // 是否是正式環(huán)境下 【我就定義isRelease這個宏】
#if isRelease == true
#define RELEASE // 正式環(huán)境下 定義RELEASE宏
#elif isRelease == false
#define DEBUG // 測試環(huán)境下 定義DEBUG宏
#endif // 結束里面的if
#endif // 結束里面的if
#endif //CLIONCPPPROJECT_T2_H // 結束外面的if
#include <iostream>
#include "T2.h"
using namespace std;
int main() {
// if 條件判斷
// ifdef xxx 是否定義了xxx這個宏
#ifdef DEBUG // 是否定義了DEBUG這個宏
cout << "在測試環(huán)境下,迭代功能" << endl;
#else RELEASE
cout << "在正式環(huán)境下,功能上下中" << endl;
#endif // 結束IF
}
2、宏的取消
// 宏的取消 #undef 宏
#include <iostream>
using namespace std;
int main() {
int i = 1
#ifndef DERRY // 如果沒有定義這個宏
#define DERRY // 我就定義宏
#ifdef DERRY // 是否定義了這個宏
for (int i = 0; i < 6; ++i) {
cout << "Derry 1" << endl;
}
#undef DERRY // 取消宏的定義,下面的代碼,就沒法用這個宏了,相當于:沒有定義過DERRY宏
#ifdef DERRY
cout << "你定義了Derry宏" << endl;
#else
cout << "你沒有定義了Derry宏" << endl;
#endif
#endif
#endif
#endif
return 0;
}
3、宏函數(shù)
優(yōu)點:文本替換,不會造成函數(shù)的調(diào)用開銷(開辟??臻g,形參壓棧,函數(shù)彈棧釋放 ..)
缺點:會導致代碼體積增大
// 宏函數(shù) 真實開發(fā)中:宏函數(shù)都是大寫
#include <iostream>
using namespace std;
#define VALUE_I 9527
#define VALUE_S "AAA"
#define VALUE_F 545.3f
#define SHOW(V) cout << V << endl; // 參數(shù)列表 無需類型 返回值 看不到
#define ADD(n1, n2) n1 + n2
#define CHE(n1, n2) n1 * n2 // 故意制作問題 ,宏函數(shù)的注意事項
// 復雜的宏函數(shù)
#define LOGIN(V) if(V==1) { \
cout << "滿足 你個貨輸入的是:" << V << endl; \
} else { \
cout << "不滿足 你個貨輸入的是:" << V << endl; \
} // 這個是結尾,不需要加 \
void show() {}
int main() {
int i = VALUE_I; // 預處理階段 宏會直接完成文本替換工作,替換后的樣子:int i = 9527;
string s = VALUE_S; // string s = "AAA";
float f = VALUE_F; // float f = 545.3f;s
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);
// 會導致代碼體積增大
show();
show();
// 普通函數(shù),每次都會進棧 彈棧 ,不會導致代碼體積增大
return 0;
}
二、JNI交互
1、JNI概述
定義:Java Native Interface,即 Java 本地接口
作用: 使得 Java 與 本地其他類型語言(如 C、C++)交互,JNI 是 Java 調(diào)用 Native 語言的一種特性,JNI 是屬于 Java 的,與 Android 無直接關系,實際中的驅(qū)動都是 C/C++開發(fā)的,通過 JNI,Java 可以調(diào)用 C/c++實現(xiàn)的驅(qū)動,從而擴展 Java 虛擬機的能力。另外,在高效率的數(shù)學運算、游戲的實時渲染、音視頻的編碼和解碼等方面,一般都是用 C 開發(fā)的(Java 代碼 里調(diào)用 C/C++等語言代碼 或 C/C++代碼調(diào)用 Java 代碼);
實際使用中,Java 需要與 本地代碼 進行交互,因為 Java 具備跨平臺的特點,所以 Java 與 本地代碼交互的能力非常弱,采用 JNI 特性 增強 Java 與 本地代碼交互的能力。
NDK 與 JNI 關系?
JNI: Java 平臺 JDK 提供的一套非常強大的框架 Java Native Interface
相互調(diào)用交互通信:C C++ Native < > Java / Kotlin
NDK: Android 平臺提供的 Native 開發(fā)工具集開發(fā)包 Native Development Kit,后面把開始的 JNI,拿到 NDK 里面來并進行封裝 (JNI,gcc,g++, )
2、JNI交互實例
簽名規(guī)則:javap -s -p MainActivity 必須是.class
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 忽略私有公開的所有屬性方法全部輸出
//MainActivity.java
package com.derry.as_jni_project;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
// 生成頭文件:javah com.derry.as_jni_project.MainActivity
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("native-lib");
}
public static final int A = 100;
public String name = "Derry"; // 簽名:Ljava/lang/String;
public static int age = 29; // 簽名:I
// Java 本地方法 實現(xiàn):native層
public native String getStringPwd();
public static native String getStringPwd2();
// ------------- 交互操作 JNI
public native void changeName();
public static native void changeAge();
public native void callAddMethod();
// 專門寫一個函數(shù),給native成調(diào)用
public int add(int number1, int number2) {
return number1 + number2 + 8;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = findViewById(R.id.sample_text);
changeName();
tv.setText(name);
changeAge();
tv.setText("" + age);
callAddMethod();
}
}
//頭文件,解釋相關細節(jié)
#include <jni.h>
#include <string>
// 解決循環(huán)Copy的問題 第二次就進不來了
#ifndef _Included_com_derry_as_jni_project_MainActivity // 如果沒有定義這個宏
#define _Included_com_derry_as_jni_project_MainActivity // 我就定義這個宏
#ifdef __cplusplus // 如果是C++環(huán)境
extern "C" { // 全部采用C的方式 不準你函數(shù)重載,函數(shù)名一樣的問題
#endif
#undef com_derry_as_jni_project_MainActivity_A
#define com_derry_as_jni_project_MainActivity_A 100L
// 函數(shù)的聲明
JNIEXPORT jstring JNICALL Java_com_derry_as_1jni_1project_MainActivity_getStringPwd
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
//native-lib.cpp
#include "com_derry_as_jni_project_MainActivity.h"
// NDK工具鏈里面的 log 庫 引入過來
#include <android/log.h>
#define TAG "Derry"
// ... 我都不知道傳入什么 借助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__)
// extern "C": 必須采用C的編譯方式,為什么,請看JNIEnv內(nèi)部源碼
// // 無論是C還是C++ 最終是調(diào)用到 C的JNINativeInterface,所以必須采用C的方式 extern "C"
// 函數(shù)的實現(xiàn)
extern "C"
JNIEXPORT // 標記該方法可以被外部調(diào)用(VS上不加入 運行會報錯, AS上不加入運行沒有問題)
// Linux運行不加入,不報錯, Win 你必須加入 否則運行報錯, MacOS 還不知道
jstring // Java <---> native 轉(zhuǎn)換用的
JNICALL // 代表是 JNI標記,可以少
// Java_包名_類名_方法名 ,注意:我們的包名 _ native _1
// JNIEnv * env JNI:的橋梁環(huán)境 300多個函數(shù),所以的JNI操作,必須靠他
// jobject jobj 誰調(diào)用,就是誰的實例 MainActivity this
// jclass clazz 誰調(diào)用,就是誰的class MainActivity.class
Java_com_derry_as_1jni_1project_MainActivity_getStringPwd
(JNIEnv * env, jobject jobj) {
}
// 靜態(tài)函數(shù)
extern "C"
JNIEXPORT jstring JNICALL
Java_com_derry_as_1jni_1project_MainActivity_getStringPwd2(JNIEnv *env, jclass clazz) {
// TODO: implement getStringPwd2()
}
extern "C"
JNIEXPORT void JNICALL
Java_com_derry_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;");
// 轉(zhuǎn)換工作
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);
// 修改成 Beyond
jstring jName = env->NewStringUTF("Beyond");
env->SetObjectField(thiz, j_fid, jName);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_derry_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);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_derry_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");
// 調(diào)用 Java的方法
jint sum = env->CallIntMethod(job, j_mid, 3, 3);
LOGE("sum result:%d", sum);
}