網頁

2012/03/04

JNI for C++

上周有個點子想要實現,時間都花在前置實驗上,在 JNI 的部份卡得比預期來得久。

一般查到的 JNI 文件似乎都是以 C 做為範例,但實際上 JNI 針對 C 與 C++ 似乎提供了不同介面?總之,用常見的那些 for C 的範例是行不通的。

剛剛重新試了一下,沒想到成功了!其實答案就在 g++ 給的 error message 中,上周大概是太累了,居然沒仔細看,白白花了那麼多冤枉工夫。

這邊簡單做個筆記。

Step 1:在 class 裡要先宣告 native method
此例為 HelloWorld 類別(HelloWorld.java)
class HelloWorld {
        private native void print();
        private native String getString(String filename);

        public static void main(String[] args) {
                System.out.println(new HelloWorld().getString(args[0]) );

        }
        static {
                System.loadLibrary("HelloWorld");
        }
}
在此處有 2 個 native method:print() 和 getString()。
System.loadLibrary() 是為了載入 native method 所在的 library。

Step 2:編譯出 class 的 byte code
$ javac HelloWorld.java
產生 HelloWorld.class。

Step 3:使用 javah 產生 JNI 相對應的 C header & source file ,並實做 .c 檔裡的方法
$ javah -jni HelloWorld
產生 HelloWorld.h 與 HelloWorld.c(若用 C++ 則自行改副檔名為 .cpp)。

HelloWorld.h(不更動)
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloWorld */

#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloWorld
 * Method:    print
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloWorld_print
  (JNIEnv *, jobject);

/*
 * Class:     HelloWorld
 * Method:    getString
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_HelloWorld_getString
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

HelloWorld.cpp(實作)
#include <jni.h>
#include <cstdio>
#include <string>
#include "HelloWorld.h"
using namespace std;

JNIEXPORT void JNICALL
Java_HelloWorld_print(JNIEnv *env, jobject obj)
{
        printf("Hello World!\n");
        return;
}

JNIEXPORT jstring JNICALL
Java_HelloWorld_getString(JNIEnv *env, jobject obj, jstring filename)
{
        string s = "the filename is: ";
        size_t strLength = 0;
        const char *buf = 0;
        strLength = (env)->GetStringLength(filename);
        buf = (env)->GetStringUTFChars( filename, 0);
        s +=  buf;
        (env)->ReleaseStringUTFChars( filename, buf);

        return (env)->NewStringUTF( s.c_str()  );
}
print() 做的事很簡單,只是印個 "Hello World!"。
getString() 則是接收傳入的 String (jstring),處理完後,再回傳。
值得注意的是這邊取得變數與回傳變數的方法是 C++ 適用的,C 的寫法不同,可參考 jni.h。

Step 4:編譯 library
g++ -shared -o libHelloWorld.so HelloWorld.cpp -I/usr/lib/jvm/java-6-openjdk/include
此例會產生一個 static shared library,-I 參數的 include file path 請指定 JDK 位置。

Step 5:執行
$ LD_LIBRARY_PATH=`pwd` java HelloWorld a.jpg
會顯示:the filename is a.jpg
設定 LD_LIBRARY_PATH 是為了讓 JVM 能在當前路徑(pwd)找到 libHelloWorld.so 並載入。

參考資料
C++和JNI的数据转换

沒有留言: