JNI開發(fā)(一)

一、宏

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);
}
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容