2013年12月16日 星期一

使用 javah 產生 Native Support / JNI 的標頭檔

在《從 HelloJNI 開始 Android NDK 的使用》這篇文章裡,可以看到 HelloJNI++ 專案的 jni/hellojni.c 裡實作了這段程式碼:

jstring
Java_demo_example_hellojni_HelloActivity_stringFromJNI( JNIEnv* env,
                                                  jobject thiz )
{
    return (*env)->NewStringUTF(env, "Hello from JNI !");
} 

而在《使用 C++ 改寫 HelloJNI++》這篇文章裡,也可以在 jni/hellojni.cpp 看到了這段程式碼:

static const char *classPathName = "demo/example/hellojni/HelloActivity";

static JNINativeMethod methods[] = {
  {"stringFromJNI", "()Ljava/lang/String;", (void*)Java_demo_example_hellojni_HelloActivity_stringFromJNI },
}; 

這兩處的程式碼,都是對應到 HelloJNI++ 專案的 Java 程式碼:

package demo.example.hellojni;

public class HelloActivity extends Activity {

    public native String stringFromJNI();

} 

那麼,這裡的 Java_demo_example_hellojni_HelloActivity_stringFromJNI()Ljava/lang/String; 如何定義呢?在網路上找到的《JNI Examples for Android》文件提供了說明;此外,也可以使用 Java JDK 的 javah 這支程式來產生。

首先,我們來看一下 javah 的用途:

Usage: javah [options] <classes>

where [options] include:

        -help                 Print this help message and exit
        -classpath <path>     Path from which to load classes
        -bootclasspath <path> Path from which to load bootstrap classes
        -d <dir>              Output directory
        -o <file>             Output file (only one of -d or -o may be used)
        -jni                  Generate JNI-style header file (default)
        -version              Print version information
        -verbose              Enable verbose output
        -force                Always write output files

<classes> are specified with their fully qualified names (for
instance, java.lang.Object). 

javah 預設的用途,就是用來生成 JNI-style 的標頭檔。

那麼,我們來看一下怎麼使用它吧!最簡單的方法,開啟一個終端程式,然後到 Android 應用程式的專案路徑執行 javah 就可以了,例如以 HelloJNI++ 這個專案來示範。

方法非常簡單,在 HelloJNI++ 專案的路徑執行這個指令:

$ javah -classpath bin/classes -d jni/ demo.example.hellojni.HelloActivity 

其中,要注意的是 classpath 的路徑要指向 bin/classes,而不是 src/ 。執行完成後,就會在 jni/ 路徑下看到生成的標頭檔 demo_example_hellojni_HelloActivity.h 了。標頭檔 demo_example_hellojni_HelloActivity.h 的內容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class demo_example_hellojni_HelloActivity */

#ifndef _Included_demo_example_hellojni_HelloActivity
#define _Included_demo_example_hellojni_HelloActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     demo_example_hellojni_HelloActivity
 * Method:    stringFromJNI
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_demo_example_hellojni_HelloActivity_stringFromJNI
  (JNIEnv *, jobject);

/*
 * Class:     demo_example_hellojni_HelloActivity
 * Method:    unimplementedStringFromJNI
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_demo_example_hellojni_HelloActivity_unimplementedStringFromJNI
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif 

往後,只要在 Java 程式碼裡宣告了 native 的函式,那麼就需要使用 javah 來重新生成這個標題檔,很容易吧!

接著再來看看,既然使用了 Eclipse 這個開發環境,那麼能不能直接將 javah 的動作整合到 Eclipse 裡呢?方法就是使用 Eclipse 的 External Tools。

從 Eclipse 的 Run 下拉選單找到 External Tools,然後點擊 External Tools Configurations。

然後選擇 Program,並新建一個設定。

接著,開始填入以下內容:

  1. Name 的部份任意填入一個容易分辨的名稱。
  2. Location 填入 javah 的路徑,由於我在 Linux 下操作,因此一般為 /usr/bin/javah
  3. Working Directory 填入 ${workspace_loc:/HelloJNI++/bin},其中 HelloJNI++ 對應到 Android 應用程式專案的名稱。也可以按下 Browse Worksapce 鈕來選擇。
  4. Arguments 填入 -classpath ${workspace_loc:/HelloJNI++/bin/classes} -d ${workspace_loc:/HelloJNI++/jni} demo.example.hellojni.HelloActivity,這個是提供給 javah 的參數,其中 HelloJNI++ 同樣對應到 Android 應用程式專案的名稱。

接著,到 Refresh 頁面,勾選 Specific resources,然後按下右側的 Specific Resources 鈕,並將 HelloJNI++ 專案裡的 jni 及 src 兩個目錄勾選起來。

設定完成後,只要從 External Tools 執行這個項目,就會生成 HelloJNI++ 專案的 JNI 標頭檔了。

張貼留言