Java如何調(diào)用C代碼

Preface

image-20210417234903849.png

在用安卓處理音視頻開發(fā)時,往往需要我們調(diào)用已有的成熟的用C/C++語言編寫的庫,比如FFmpeg,LAME等.這就牽涉到如何用Java調(diào)用C語言編寫的庫.

本文處理的是最簡單的情形,簡單調(diào)用一個C文件里的函數(shù).

想寫這篇文章很久了,但是之前遇到了一個坑,今天才萬幸走出來.所以趕緊記錄一下.

1 Java調(diào)用C代碼的整體流程

Java調(diào)用C代碼是通過JNI(JavaNativeInterface)這個手段來實(shí)現(xiàn)的,具體流程見下圖:


image-20210418001615813.png

接下來我們用一個實(shí)例來說明如何實(shí)現(xiàn)Java調(diào)用C代碼

2 準(zhǔn)備工作

  • 確認(rèn)開發(fā)環(huán)境

小編這里使用的是

MacOS 11.0.1

Android Stuidio 4.1.2

Javac 的版本是14.0.1,這個很重要,影響到了第2步生成jni文件.之前參考帖子上用的都是javah命令,但是在小編這里就是不成功,可能就是版本問題.

  • 明確兩個重要工具的目錄

ndk-build程序目錄:

/Users/gikkiares/Library/Android/sdk/ndk/20.0.5594570/ndk-build

javac目錄

/usr/bin/javac

如果在使用命令時,提示command not found:就說明要么工具沒有安裝,要么安裝了,但是沒有加入到環(huán)境變量.

如果是前者,重新安裝對應(yīng)工具.

如果是后者,將工具的目錄加入到環(huán)境變量,或者使用命令的全路徑.

  • 創(chuàng)建一個新的Android項(xiàng)目

我們在該項(xiàng)目中,實(shí)現(xiàn)用java調(diào)用c代碼.

3 Java調(diào)用C代碼示例

3.1 編寫java橋接類

為了簡單起見,我們新建一個CManager.java

CManager類負(fù)責(zé)兩個事情:

1,對java層,提供了java形式的接口

2,實(shí)現(xiàn)的方式為c語言.

該文件內(nèi)容為:

package com.tinywind.gajdemo.module.cmanager;

public class CManager {
    public static CManager sharedInstance = new CManager();
    public native String getMessageFromC();
    public native int sum(int a, int b);
}

我們注意到以下幾點(diǎn):

  • 我們使用了單例模式.
  • 定義了兩個用native關(guān)鍵字修飾的方法.

1.使用native關(guān)鍵字,表示我們實(shí)現(xiàn)該方法的語言不是java而是c/c++.

2,getMessageFromC,簡單地用c語言返回一個字符串

3,sum函數(shù),用c語言實(shí)現(xiàn)兩個數(shù)相加.

  • 我們只聲明了函數(shù),并沒有實(shí)現(xiàn).

3.2 生成jni風(fēng)格的頭文件.

小編之前一直卡在這一步.一直在嘗試用javah命令生成,單總是提示找不到類.

估計可能和小編用的javac版本是14.0.1有關(guān).

總之,錯誤的方式成千上萬,正確的方式只有那么一種,我們記住正確的就好了,錯誤的就讓他隨風(fēng)而去吧~

//切換到CManager.java所在的目錄
cd ${ProjectPath}/app/src/main/java/com/tinywind/gajdemo/module/cmanager
//-h .指定了在當(dāng)前目錄中輸出jni風(fēng)格的頭文件
javac CManager.java -h .

這一步完成之后,我們得到了一個.class文件和.h文件


image-20210417173817694.png

.class文件對我們來說沒用,可以直接刪除.

這個很長的h文件,就是我們的jhi頭文件.

3.3 編寫C文件

新建目錄

src/main/jni

需要將jni的頭文件移動到這個目錄中.

然后新建一個文件CManager.c,內(nèi)容為:

#include "com_tinywind_gajdemo_module_cmanager_CManager.h"


    //C字符串轉(zhuǎn)java字符串
    jstring cstringToJstring(JNIEnv* env, const char* pStr) {
        int        strLen    = strlen(pStr);
        jclass     cls_string   = (*env)->FindClass(env, "java/lang/String");
        // 獲取java String類方法String(byte[],String)的構(gòu)造器,用于將本地byte[]數(shù)組轉(zhuǎn)換為一個新String
        jmethodID  methodId  = (*env)->GetMethodID(env, cls_string, "<init>", "([BLjava/lang/String;)V");
        jbyteArray byteArray = (*env)->NewByteArray(env, strLen);
        jstring    encode    = (*env)->NewStringUTF(env, "utf-8");

        (*env)->SetByteArrayRegion(env, byteArray, 0, strLen, (jbyte*)pStr);

        return (jstring)(*env)->NewObject(env, cls_string, methodId, byteArray, encode);
    }

JNIEXPORT jstring JNICALL Java_com_tinywind_gajdemo_module_cmanager_CManager_getMessageFromC
  (JNIEnv * env, jobject obj) {
    char * str = "Hello,this is a message from c!";
    return cstringToJstring(env, str);
  }

  JNIEXPORT jint JNICALL Java_com_tinywind_gajdemo_module_cmanager_CManager_sum
    (JNIEnv * env, jobject obj, jint a, jint b) {
    return a + b;
    }

需要注意以下幾點(diǎn):

  • c文件中要引入jni頭文件#include "com_tinywind_gajdemo_module_cmanager_CManager.h"
  • 函數(shù)的原型從jni頭文件中拷貝,但是要注意jni頭文件中形參的名稱是省略的,我們需要加上去.
  • c語言的字符串和jni的jstring是不同類型的需要轉(zhuǎn)換.

3.4 生成動態(tài)庫

在jni目錄中新建Android.mk文件,目錄看起來是這個樣子:


image-20210418005701072.png

在Android.mk文件中輸入以下內(nèi)容:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libcmanager
LOCAL_SRC_FILES := ./CManager.c
include $(BUILD_SHARED_LIBRARY)
  • LOCAL_MODULE指定了so庫的名字

要注意的是,寫得是libcmanager,但是后期導(dǎo)入時只需要寫cmanager.

  • LOCAL_SRC_FILES指定了so庫的源文件

其他的項(xiàng)目暫時不太清楚

然后執(zhí)行以下命令:

//然后切換到目錄:
${ProjectPath}/app/src/main
//編譯c文件為so動態(tài)鏈接庫
${NdkDir}/ndk-build
image-20210418074249795.png

編譯成功之后,項(xiàng)目里在src/main目錄下會多出libs目錄,里面對應(yīng)不同的架構(gòu),產(chǎn)生了對應(yīng)的so庫.

3.5 加載動態(tài)鏈接庫

加載動態(tài)鏈接庫最簡單的方法,就是在main目錄下創(chuàng)建jniLibs文件夾,然后將libs中目全部加入進(jìn)去就額可以了.

系統(tǒng)會自動加載jniLibs里的so動態(tài)庫.

3.6 調(diào)用Native方法

最后,我們調(diào)用Native方法,只要把橋接的Java類CManager當(dāng)做普通的java類去調(diào)用就可以了:

        String string = CManager.sharedInstance.getMessageFromC();
        int sub = CManager.sharedInstance.sum(1,1);

我們斷點(diǎn)查看執(zhí)行結(jié)果

image-20210418075408617.png

我們可以看到,c的世界向java的世界發(fā)來了賀電"Hello,this is a message from c",然后也友好地教導(dǎo)了我們1+1=2.

這就是java調(diào)用c代碼的最簡單模型.

4 總結(jié)

  • 關(guān)于Markdown的一個問題

希望markdown有一個沒有級別的標(biāo)題,現(xiàn)在是要么1級標(biāo)題,2級標(biāo)題.

我希望能有一個沒有級別的標(biāo)題.

  • 關(guān)于模板

模板,就是套路.不管是在編程界還是社交界,都是需要各種模板.

模板夠多,才能玩的六.

所以本文牽涉的內(nèi)容很簡單,但是也是一個很重要的模板

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

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

  • 夜鶯2517閱讀 128,218評論 1 9
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月,有人笑有人哭,有人歡樂有人憂愁,有人驚喜有人失落,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,898評論 28 54
  • 兔子雖然是枚小碩 但學(xué)校的碩士四人寢不夠 就被分到了博士樓里 兩人一間 在學(xué)校的最西邊 靠山 兔子的室友身體不好 ...
    待業(yè)的兔子閱讀 2,790評論 2 9
  • 信任包括信任自己和信任他人 很多時候,很多事情,失敗、遺憾、錯過,源于不自信,不信任他人 覺得自己做不成,別人做不...
    吳氵晃閱讀 6,392評論 4 8

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