1. 程式人生 > >編寫JNI的兩種應用層與JNI層方法對映方式

編寫JNI的兩種應用層與JNI層方法對映方式

通常我們在編寫的JNI 時,在定義上層應用層需要呼叫的函式中,我們需要對該函式進行應用層與JNI層方法之間的對映。這樣上層的Android應用程式才能正確的呼叫我們的JNI函式,這種對映的方式一共有兩種。

在函式名中進行對映
在函式名中進行對映是最為簡單的一種方法,因為只要我們知道呼叫我們JNI函式的Java檔案所在的路徑,那麼我們就將該路徑放在我們JNI對應函式的前面就可以了,同時還要在函式前面加上Java,路徑間用”_”進行分隔。例如,如果我們的應用程式中這樣載入動態連結庫:

package com.intel.jni;

public class CC1100DataSource 
{
public native int Open(); public native int Close(); public native byte[] Read( int len); public native int Write(char[] buf , int len); public static CC1100DataSource cc1100instance; public static CC1100DataSource getCC1100DataSource() { cc1100instance = new CC1100DataSource(); return
cc1100instance; } static { System.loadLibrary("cc1100"); } }

由上面的應用程式我們知道該java檔案放在com/intel/jni包中,同時檔名為CC1100DataSource,那麼我們在編寫相應的jni函式時就應該這樣編寫,這裡以open函式為例:

/*
 * Class:     cc1100
 * Method:    Open
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_com_intel_jni_CC1100DataSource_Open
  (JNIEnv *env
, jobject obj) { if(fd<=0)fd = open("/dev/cc1100", O_RDWR|O_NDELAY|O_NOCTTY); if(fd <=0 )__android_log_print(ANDROID_LOG_ERROR, "serial", "open /dev/cc1100 Error"); else __android_log_print(ANDROID_LOG_INFO, "serial", "open /dev/cc1100 Sucess fd=%d\n",fd); }

可以看到我們的函式名為Java_com_intel_jni_CC1100DataSource_Open,在open前面分別有Java和Java檔案所處的路徑。這樣編寫之後,我們的jni就和上層呼叫的Java程式就建立好連線了。

採用註冊本地方法的方式
這種方式相對於前一種在編寫的時候要稍微複雜一點,但是定義好了之後,會更加靈活。在我們實際的開發過程中會更加傾向該種方式。該種方式在定義的時候主要分為三步:

第一步:建立對映連線
建立對映連線需要使用到JNINativeMethod結構體型別的陣列,JNINativeMethod型別的資料主要由三個部分組成,第一個是定義上層應用程式的呼叫函式的名,第二個是定義函式返回型別和引數型別。第四個是其對應的JNI函式名。

typedef struct {

const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;

下面就以我們通用的open、read、write、close函式作為例子來進行對映。

static const JNINativeMethod methods[] =
{
        {"_open", "()I", (void *) Radar_Open},
        {"_read", "(I)[B", (void *) Radar_Read},
        {"_write", "(C)I", (void *) Radar_Write},
        {"_open", "()I", (void *) Radar_Open},
};

上面建立了一個JNINativeMethod型別的陣列,其中open函式為返回值為int型別,無引數。read函式引數為int型別資料,返回值型別為byte陣列。關於這種表示方式可以參照博文Android JNI 使用的資料結構JNINativeMethod詳解

第二步:採用RegisterNatives註冊第一步所建立好的對映
在這一步中我們要將之前建立的函式對映註冊進動態連結庫檔案,同時由於在上一步建立對映的過程中我們並沒有定義好上層呼叫的Java檔案的路徑和檔名,所以在註冊的時候我們也要一一定義。
這裡我還是以自己編寫的一個radar驅動程式為例:

int register_radar_jni(JNIEnv* env)
{
    static const char* const kClassName = "com/intel/jni/radar";
    jclass clazz;
    clazz = (*env)->FindClass(env,kClassName);

    if(clazz == NULL)
    {
        LOGE("Can't find class %s\n", kClassName);
        return -1;
    }

    if((*env)->RegisterNatives(env,clazz, methods, sizeof(methods)/sizeof(methods[0]))!= JNI_OK)
    {
        LOGE("Failed registering methods for %s\n", kClassName);
        return -1;
    }
    return 0;
}

從上面我們也可以體會到,每次當我們需要更改上層呼叫的java檔案的路徑或者是檔名時,只需要更改kClassName所指定的路徑就行,而不需要像第一種方式那樣要將所有與之對映的檔案的JNI函式全部都更改名字。

第三步:將上一步的註冊函式放入JNI_OnLoad方法當中
注意這個JNI_OnLoad方法屬於JNI的系統方法,當我們在上層java程式中呼叫System.loadLibrary(“cc1100”);時就會首先去呼叫JNI檔案裡面的JNI_OnLoad函式。平時如果我們沒有寫JNI_Onload方法,則表示JNI_Onload方法為空。

jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    JNIEnv* env = NULL;
    jint result = -1;

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

    register_radar_jni(env);
    return JNI_VERSION_1_4;
}

需要注意的是對於JNI檔案,我們既可以採用C++的語言編寫,也可以採用C語言編寫,如果採用C語言編寫,可能在有些語法規則上面會有一些細微區別。例如我們在使用env的時候,如果是C++語言,則為:env->RegisterNatives,而如果是C語言的話,便是(*env)->RegisterNatives。網上有一篇關於JNIEnv的介紹JNIEnv解析