【Android JNI】從Java中調用C/C++

Android系統加載JNI Lib的方式

要想在Java中調用C的函數,必然要有一定的規則去映射二者的函數名,也就是加載JNI庫的方式,下面介紹這兩種方式。

JNI_OnLoad

當Android的VM(Virtual Machine)執行到C組件(即*so)裏的System.loadLibrary()函數時,
首先會去執行C組件裏的JNI_OnLoad()函數。這種方法有兩個優點,1. 可以通知VM此時native使用哪個版本的JNI,默認是最低版本;2. 使得native層在被加載的時候做一些初始化工作。
JNI_OnLoad()中聲明的JNI函數在進程空間中的起始地址被保存在ClassObject->directMethods中,也就是說在加載so的時候就記錄下來了JNI函數的地址,等到使用的時候直接從特定地址執行相關函數就可以了。
JNI_OnLoad函數中最重要的事就是調用RegisterNatives函數完成動態庫中JNI函數的註冊,jint RegisterNatives(jclass clazz, const JNINativeMethod* methods, jint nMethods)這個函數的第二個參數JNINativeMethod是如下的結構體:

typedef struct {
    const char* name;
    const char* signature;
    void*       fnPtr;
} JNINativeMethod;

name爲java中定義的native函數的函數名,signature爲函數的簽名,fnPtr爲對應的native C函數的函數指針。
函數簽名查看方法,比如有Example類,先編譯javac Example.java,然後使用命令javap -s -p Example.class可以查看函數和成員變量的簽名。

示例:JNI_OnLoad

假設Java中有 public class JniManager { public native String nGetStudentInfo(); }方法,native C中定義的對應的函數爲jstring jniGetStudentInfo(JNIEnv *env, jobject object),具體的JNI_OnLoad代碼如下所示:

static const JNINativeMethod gMethods[] = {
        {"nGetStudentInfo",  "()Ljava/lang/String", (void *) jniGetStudentInfo},
        {"nHello",           "()V",                 (void*) jniHello}
};

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {

    JNIEnv *env = NULL;
    jint result = -1;

    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        return result;
    }

    jclass clazz = env->FindClass("com/lmshao/jniexample/JniManager");
    if (clazz == NULL) {
        return result;
    }

    jint count = sizeof(gMethods) / sizeof(gMethods[0]);

    if (env->RegisterNatives(clazz, gMethods, count) != JNI_OK) {
        return result;
    }

    result = JNI_VERSION_1_6;

    return result;
}

JNIEXPORTJNICALL爲jni.h中的宏定義,
#define JNIEXPORT __attribute__ ((visibility ("default")))JNIEXPORT用來導出動態庫的函數符號,JNICALL暫時定義爲空。

dvmResolveNativeMethod延遲解析機制

如果JNI Lib中沒有JNI_OnLoad函數,即在執行System.loadLibrary時,
無法把此JNI Lib實現的函數在進程中的地址增加到ClassObject->directMethods。則直到需要調用的時候纔會解析這些javah風格(包名+類名+方法名)的函數。這種方法是Android Studio默認的native工程使用的方法。
示例:
比如Java裏面MainActivity類中有個方法聲明爲public native String stringFromJNI();,則Native中對應的函數名字應該是jstring Java_com_example_demo_MainActivity_stringFromJNI(){}
雖然這種方法是Android Studio默認Native工程使用的方法,但是因爲種種原因在實際Native開發中很少使用。

示例

Android Studio 3新建一個Native工程,默認有如下示例函數。

MainActivity.java

public class MainActivity extends AppCompatActivity {

    // 加載動態庫
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }

    // Native方法
    public native String stringFromJNI();
}

native-lib.cpp

#include <jni.h>
#include <string>

extern "C" 
JNIEXPORT jstring JNICALL
Java_com_example_myapplication_MainActivity_stringFromJNI(JNIEnv *env, jobject) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

參考:
- https://blog.csdn.net/fireroll/article/details/50102009

簽名

JNI簽名是使用一些縮寫符號來代表參數類型,這些符號由Java語言規定的。

數據類型的簽名

Java基本數據類型的簽名如下表所示:

簽名 Java類型 JNI類型
Z boolean jboolean
C char jcahr
B byte jbyte
S short jshort
I int jint
J long jlong
F float jfloat
D double jdouble
[B byte[ ] jbyteArray
[I int[ ] jintArray

Java複雜類型簽名格式是:“L”+“全限定類名”+“;” 。例如String類型簽名爲Ljava/lang/String;,對應的JNI類型爲jstring。其餘的複雜數據類型對應的JNI類型都是jobject,jstring本質上也是jobejct類型,因爲使用頻率高,所以又單獨定義方便使用。

函數參數簽名

函數的參數簽名由參數和返回值組成,參數用一對小括號包起來,即使參數爲空也要使用空括號,括號後面是返回值類型,如果沒有返回值則用字母V表示。
如:(I)V表示參數爲int,無返回值。()I表示參數爲空,返回值爲int。([IZ)I表示參數爲int[]和boolean,返回值爲int。
參考:
劉超. 深入理解Android 5.0系統[M]. 人民郵電出版社, 2015.

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