2011年8月16日 星期二

Java Native Interface (JNI) Android C++語言篇--以Hello-JNI為例

在Hello-JNI的例子中我們可以看到在螢幕show出的指令C語言寫法如下:
return (*env)->NewStringUTF(env, "Hello from JNI !");

然而我們使用Hello-JNI的程式改成C++語言,此時寫法就會變成下面的寫法:
return env->NewStringUTF( (char *)"Hello from JNI!");

接著將
1.檔名改 hello-jni.cpp
2.修改 Android.mk 中 LOCAL_SRC_FILES := hello-jni.cpp
3.使用ndk-build編譯 (可以順利編譯完成)
再用手機或模擬器執行,然後可以發現程式發生錯誤 ?!! Why??不是說NDK支援C/C++嗎??



此時使用logcat檢查看看…可以發現到下列錯誤 :
No implementation found for native Lcom/example/hellojni/HelloJni;.stringFromJNI ()Ljava/lang/String;

要解這個問題可以參考JNI Examples for Android這個PDF檔案中4.4The JNI OnLoad() function implementation部分。
所以整個 hello-jni.cpp程式如下:




程式中下述在宣告JNINativeMethod部分中有三個部分(藍、綠、紅)要注意的:


第一個參數(藍色字部分) name 是 Java 中函數的名字。
第二個參數(綠色字部分) signature,用字串是描述了函數的參數和返回值。
第三個參數(紅色字部分) fnPtr是函數指標,指向C函數。
比較注意的是第二個參數,例如
"()V"
"(II)V"
"(Ljava/lang/String;Ljava/lang/String;)V"

這些字元是與函數的參數類型相對應的。
"()" 中的字元表示參數,後面的則代表返回值。例如"()V" 就表示void Func();
"(II)V" 表示 void Func(int, int);

對應關係如下表:


陣列部分則以"["開頭,用兩個字元表示:


上述的部分都是基本類型。如果 Java 函數的參數是 class,則以 "L" 開頭,以 ";" 結尾中間是用 "/" 隔開的包及類名。而其對應的C函數名的參數則為 jobject 一個例外是String類,其對應的類為 jstring
Ljava/lang/String; String jstring
Ljava/net/Socket; Socket jobject

如果JAVA函數位於一個嵌入類,需用 $ 作為類名分隔符號,例如: (Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z"

另外最後,還要宣告 classPathName (黃色字部分) 這個部分就比較簡單只要填寫是由哪隻程式呼叫C++的Class路徑全名填寫進去就可以了。

JNI_OnLoad Function 程式碼 :(將下面程式碼貼在程式後面既可解決JNI_OnLoad)

//JNI_OnLoad Function begin.....
/*
 * Register several native methods for one class.
 */
static int registerMethods(JNIEnv* env) {
static const char* const kClassName = "com/example/helloworld/MainActivity";
static JNINativeMethod gMethods[] = { { "stringFromJNI",
"()Ljava/lang/String;",
(void*) Java_com_example_helloworld_MainActivity_stringFromJNI }, };
jclass clazz;
/* look up the class */
clazz = env->FindClass(kClassName);
if (clazz == NULL) {
return -1;
}

/* register all the methods */
if (env->RegisterNatives(clazz, gMethods,
sizeof(gMethods) / sizeof(gMethods[0])) != JNI_OK) {
return -1;
}
/* fill out the rest of the ID cache */
return 0;
}
/*
 * This is called by the VM when the shared library is first loaded.
 */
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
goto fail;
}
if (registerMethods(env) != 0) {
goto fail;
}
/* success -- return valid version number */
result = JNI_VERSION_1_4;
fail: return result;
}
//JNI_OnLoad Function end.....



參考:
1.JNI Examples for Android
http://android.wooyd.org/JNIExample/files/JNIExample.pdf

2.Android JNINativeMethod詳解
http://newfaction.net/2010/12/09/android-jninativemethod-detailed.html

3.JNI與Android VM之關係
http://www.android1.net/?Forum11/thread-789-1-4
這是高煥堂老師的文章,其中內容說明到:
當VM執行到System.loadLibrary()函數時,首先會去執行C組件裡的JNI_OnLoad()函數。它的用途有二:
(1). 告訴VM此C組件使用那一個JNI版本。如果你的*.so檔沒有提供JNI_OnLoad()函數,VM會默認該*.so檔是使用最老的JNI 1.1版本。由於新版的JNI做了許多擴充,如果需要使用JNI的新版功能,例如JNI 1.4的 java.nio.ByteBuffer, 就必須藉由JNI_OnLoad()函數來告知VM。
(2). 由於VM執行到System.loadLibrary()函數時,就會立即先呼叫JNI_OnLoad(),所以C組件的開發者可以藉由JNI_OnLoad()來進行C組件內的初期值之設定(Initialization)。


===========延伸閱讀========================================
第一支Android NDK程式--HelloJni

Java Native Interface (JNI) 的使用時機及影響

Java Native Interface (JNI)入門 -- 觀念篇

Java Native Interface (JNI) 實戰篇

Java Native Interface (JNI) Android實戰篇(使用NDK) -- HelloUart

Java Native Interface (JNI) Android算數篇(使用NDK) -- Fibonacci Sequence

Java Native Interface (JNI) Android C++語言篇--以Hello-JNI為例

7 則留言:

  1. 在 Android 的 source code 裡,可以找到 development/samples/SimpleJNI 這個目錄,其中 jni/native.cpp 也可以找到這段程式碼。

    不過,若要單獨拿這個範例來改用,請先刪掉以下這行:

    #include

    然後,將範例裡的 LOGE(); 改成本文裡蚊子兄所貼的 fprintf()。

    回覆刪除
  2. 您好
    我翻網路上幾篇文章
    有提到需要修改Application.mk和Android.mk的內容
    但這裡為何不需要呢?

    回覆刪除
  3. 作者已經移除這則留言。

    回覆刪除
    回覆
    1. 在NDK解開的目錄下會有一個/samples目錄中....

      刪除
  4. 不好意思蚊子大大..我想再請問一下,我現在在jni C裡面寫一個code處理由java端傳來的資料再回傳給Java, 其中有個函式我用到JNIEXPORT void JNICALL xxxx時全部的code都會有黃色波浪底線 說syntax error..可是程式照樣可以compile

    假如我單純void 的話就沒事, 請問一下這兩者的差異在哪裡??

    還有我現在程式在LogCat中 No implementation found for native Lqoo/example/.../..;.getdata ([B)V 整段是顯示warning,

    不過我有UnsatisfiedLinkError : getdata 的錯誤
    在其他網頁找過有遇到類似問題的人,他們說可能函式大小寫有打錯,不過我確認過了還是一樣,或者libs整個砍掉再ndk-build生成新的.so檔完後也一樣..不太清楚還有可能是哪裡有問題? 會是我在java端引用這個jni C程式的方法錯了嗎?或者是其他原因?

    不好意思一直問怪問題..我現在完全自學、找不到人教很傷腦筋@@..再麻煩你了

    回覆刪除
    回覆
    1. 通常你所講的問題經常發生(連我也在內!!),你可以參考"使用 javah 產生JNI C語言標頭檔 .h的正確語法"這篇去產生*.h檔以裡面的參數為準是最不會出問題的,又或者參考"Java Native Interface (JNI) Android實戰篇(使用NDK) -- HelloUart"第5點有說明一些,如果還沒辦法解決的話.....那你可以搜尋Java JNI部分,希望能幫上您!

      刪除
  5. Hi
    你應該是遇到C/C++ function export的問題,
    可以試著把檔名改成hello-jni.c,或者在JNIEXPORT 前面加上extern "C",
    應該就可以解決你的問題了.

    回覆刪除