通過示例去看JNI中爲什麼使用extern “C“

經驗總結

在JNI開發過程中,我們使用C++去寫一個動態庫,由於C++編譯器對於函數的符號的生成需要進行名字修飾處理,然後生成的函數符號不再跟源代碼中定義的函數名一致
這樣導致調用方通過函數名去調用我們的函數(用函數名充當函數符號去查找函數地址),將會找不到具體的實現,然後崩潰。
JVM調用我們寫的native接口/接口,就是這種情況。
所以當我們使用C++去寫navtive的接口時,需要用extern “C” 包住native接口,這樣就顯示告訴C++編譯器,對於我們的接口/函數使用C語言的方式(函數符號即函數名稱)去編譯代碼和生成函數符號。
如下是CPP源碼中native接口的實現沒有使用extern “C” 聲明,運行時出現的崩潰棧

2020-06-30 18:45:22.522 10202-10202/? E/art: No implementation found for java.lang.String com.example.hellolibs.MainActivity.stringFromJNI() (tried Java_com_example_hellolibs_MainActivity_stringFromJNI and Java_com_example_hellolibs_MainActivity_stringFromJNI__)
2020-06-30 18:45:22.523 10202-10202/? E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.hellolibs, PID: 10202
    java.lang.UnsatisfiedLinkError: No implementation found for java.lang.String com.example.hellolibs.MainActivity.stringFromJNI() (tried Java_com_example_hellolibs_MainActivity_stringFromJNI and Java_com_example_hellolibs_MainActivity_stringFromJNI__)
        at com.example.hellolibs.MainActivity.stringFromJNI(Native Method)
        at com.example.hellolibs.MainActivity.onCreate(MainActivity.java:31)
        at android.app.Activity.performCreate(Activity.java:6813)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1119)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2805)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2927)
        at android.app.ActivityThread.-wrap12(ActivityThread.java)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1650)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:159)
        at android.app.ActivityThread.main(ActivityThread.java:6364)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1096)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:883)

如上案例,對應的so中的函數符號是

_Z53Java_com_example_hellolibs_MainActivity_stringFromJNIP7_JNIEnvP8_jobject

示例說明

  1. 本文使用的示例是google官方的JNI demo中的hello-libshello-jni
    在這裏插入圖片描述
  2. 另外使用llvm-readelf來查看so中的符號信息

示例分析

hello-jni示例

hello-jni的項目結構結下圖所示,關鍵的hello-jni.c的代碼如下
在這裏插入圖片描述

我們進去看下so中的符號,就是看到定義的native接口的名稱跟函數符號是一致的

dw_luogongwu@dw-luogongwudeMacBook-Pro hello-jni$ ff *.so
./app/build/intermediates/cmake/arm8Debug/obj/armeabi-v7a/libhello-jni.so
./app/build/intermediates/cmake/arm8Debug/obj/arm64-v8a/libhello-jni.so
./app/build/intermediates/merged_native_libs/arm8Debug/out/lib/armeabi-v7a/libhello-jni.so
./app/build/intermediates/merged_native_libs/arm8Debug/out/lib/arm64-v8a/libhello-jni.so
./app/build/intermediates/stripped_native_libs/arm8Debug/out/lib/armeabi-v7a/libhello-jni.so
./app/build/intermediates/stripped_native_libs/arm8Debug/out/lib/arm64-v8a/libhello-jni.so

dw_luogongwu@dw-luogongwudeMacBook-Pro out$ llvm-readelf --symbols lib/arm64-v8a/libhello-jni.so | grep FUNC
     3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __cxa_finalize@LIBC
     4: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __register_atfork@LIBC
     5: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __cxa_atexit@LIBC
    11: 0000000000000608    64 FUNC    GLOBAL DEFAULT   10 Java_com_example_hellojni_HelloJni_stringFromJNI
    40: 00000000000005c0    12 FUNC    LOCAL  DEFAULT   10 __on_dlclose
    41: 00000000000005d0     4 FUNC    LOCAL  DEFAULT   10 __on_dlclose_late
    53: 00000000000005fc    12 FUNC    LOCAL  DEFAULT   10 pthread_atfork
    55: 00000000000005d4    12 FUNC    LOCAL  DEFAULT   10 __atexit_handler_wrapper
    59: 00000000000005e0    28 FUNC    LOCAL  DEFAULT   10 atexit
    60: 00000000000005cc     4 FUNC    LOCAL  DEFAULT   10 __emutls_unregister_key
    63: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __cxa_finalize@@LIBC
    64: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __register_atfork@@LIBC
    71: 0000000000000608    64 FUNC    GLOBAL DEFAULT   10 Java_com_example_hellojni_HelloJni_stringFromJNI
    72: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __cxa_atexit@@LIBC

總結:使用C來寫動態庫,不需要使用extern

hello-libs示例

hello-libs的項目結構,跟關鍵的代碼如下圖所示
在這裏插入圖片描述
如下圖是hello-lib使用與去掉extern後,native接口對應的函數符號的對比
在這裏插入圖片描述

總結: 使用C++來寫動態庫,需要extern “C” 來聲明我們實現的natvie的接口/函數

其它說明

通過函數符號調用函數

這個屬於dlopen/dlsum的使用範疇,即運行時加載一個動態庫,並通過符號找到函數地址,通過函數針指方式調用函數
具體可以看參考文檔中的 C語言調用so動態庫的兩種方式

有關C++的名字修飾

在C/C++中,一個程序要運行起來,需要經歷以下幾個階段:預處理、編譯、彙編、鏈接。
名字修飾(Name Mangling)是一種在編譯過程中,將函數、變量的名稱重新改編的機制,
簡單來說就是編譯器爲了區分各個函數,將函數通過一定算法,重新修飾爲一個全局唯一的名稱。

具體可以看篇文章 >> C+±-名字修飾

有關llvm-readelf的配置與使用

我使用的是ndk自帶的llvm,我把llvm的bin目錄放到了系統環境變量中,方便使用所有用llvm-xxx命令
在.bash_profile配置如下

# for android-ndk env
ANDROID_NDK_LLVM_BIN=${HOME}/Library/Android/sdk/ndk/21.2.6472646/toolchains/llvm/prebuilt/darwin-x86_64/bin
PATH=$PATH:$ANDROID_NDK_LLVM_BIN
export PATH

參考文檔

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章