JNI動態註冊以及JNI簽名

2023-09-14 18:00:59

一.動態註冊和靜態註冊

  註冊native方法有兩種方式,動態註冊和靜態註冊。靜態註冊是在編譯時進行註冊,而且在java中宣告的native方法和c/c++中的本地方法的對應關係是恆定的;比如說在com.example.test包下的Test類中宣告了一個stringFromJNI()的native方法,那麼它對應的c/c++中的本地方法名就是Java_com_example_test_Test_stringFromJNI();並且這個方法名不能做任何的修改,在java中呼叫stringFromJNI()函數時,就會按包名_類名_方法名的形式找到對應的方法並呼叫。而動態註冊是在執行時進行註冊的,而且本地方法的名字可以按自己的喜好隨意取,只要說明了java中宣告的native方法和c/c++中的本地方法的對應關係即可。下面用程式碼的形式來演示一下動態註冊的使用步驟。

二.動態註冊的步驟

  1.在java中宣告native方法,並在靜態程式碼塊中載入動態庫:

public class Test {
    static{
        System.loadLibrary("dynamic"); //載入動態庫
    }
  //宣告native方法 native String stringFromJNI(); native static
int add(int a,int b); }

  2.註冊函數:在java中載入動態庫的時候,虛擬機器器會呼叫JNI庫中的JNI_Onload()函數,動態註冊就是在這個函數中進行的。動態註冊使用的是RegisterNatives()方法,這個方法接收3個引數,分別是:

    1.jclass clazz  宣告native方法的java類

    2.const JNINativeMethod* methods  JNINativeMethod型別的結構體陣列,我們就是在這個結構體陣列中說明java方法和本地方法的對應關係的

    3.jint nMethods  第二個引數methods所指向的結構體陣列的大小

    JNINativeMethod結構體的定義如下:

    typedef struct {
        const char* name;//java中的方法名
        const char* signature;//jni簽名
        void*       fnPtr;//本地函數的指標
    } JNINativeMethod;

    下面給出這部分的程式碼:

#include <jni.h>
#include <string>
using namespace std;

/**
*實現本地方法,名字可以任取,方法的前兩個引數是固定的,後面的引數就是實際的引數
*/ jstring native_stringFromJNI(JNIEnv
*env,jobject thiz){ return env->NewStringUTF("nihao"); } jint native_add(JNIEnv *env,jobject thiz,jint a,jint b){ return a+b; } static const JNINativeMethod nativeMethod[]={ {"stringFromJNI","()Ljava/lang/String;", (void *) native_stringFromJNI}, {"add","(II)I",(void *)native_add} }; static int registerNativeMethod(JNIEnv *env){ int result=-1; jclass clazz=env->FindClass("com/example/dynamicregister/Test"); if(env->RegisterNatives(clazz,nativeMethod,sizeof(nativeMethod)/sizeof(nativeMethod[0]))==JNI_OK){ result=0; } return result; } jint JNI_OnLoad(JavaVM *vm,void *reserved){//這個方法是一個override方法,在載入動態庫時,會自動呼叫,一般用來做一些初始化操作,動態註冊的程式碼就可以寫在這 JNIEnv *env= nullptr; if(vm->GetEnv((void **)&env,JNI_VERSION_1_4)==JNI_OK){//首先需要獲取JNIEnv *env指標,因為registerNativeMethod方法會用到 if(registerNativeMethod(env)==0){ return JNI_VERSION_1_4; //返回值代表動態庫需要的jni版本 } } return -1; }

  3.在java中呼叫native函數:

public class MainActivity extends AppCompatActivity {
    private TextView tv_display;

    @SuppressLint("MissingInflatedId")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv_display=findViewById(R.id.tv_display);
        tv_display.setText(Test.add(1,10)+"");
    }
}

三.JNI簽名

  下面我把和簽名有關的程式碼單獨拿出來進行說明:

static const JNINativeMethod nativeMethod[]={
        {"stringFromJNI","()Ljava/lang/String;", (void *) native_stringFromJNI},
        {"add","(II)I",(void *)native_add}
};

  比如第一個函數的簽名:()Ljava/lang/String;其實表示的是java中的stringFromJNI函數的形參為空,返回值型別為String。

  第二個函數的簽名(II)I表示java中的add函數的形參列表是(int,int),返回值型別也是int型別。

  那這個簽名有什麼作用呢?其實是為了解決java中的函數過載問題。比如,如果java中還宣告了一個方法,native String stringFromJNI(String str);那麼如果沒有函數簽名的話,就不知道c/c++中的native_stringFromJNI()對應的是java中的哪個stringFromJNI函數。

  接下來,通過一個表格來說明JNI基本型別的簽名以及參照型別的簽名:

   

   特別需要注意的是,參照型別簽名後面的分號;一定不能省略,否則編譯通過不了。