http://87showmin.blogspot.com/2009/06/java-java-native-interfacejni.html
JNI 是用來讓Java跟別種語言溝通的函式庫,如果我們舉C/C++ 為例,便分為C call Java與Java call C。
Java call C
這段是參考[1]的第二章。
1. 建立一個 Java class (HelloWord.java)。在這個class裡宣告一個native method (print),然後在 main() 裡呼叫這個 method。此時,我們呼叫 printf() 時,它的實體是用 C/C++ 所寫的。
01.
class
HelloWorld {
02.
private
native
void
print();
03.
public
static
void
main(String[] args) {
04.
new
HelloWorld().print();
05.
}
06.
static
{
07.
System.loadLibrary(
"HelloWorld"
);
08.
}
09.
}
上例中的System.loadLibrary("HelloWorld")
會去找你程式目錄下的HelloWorld.dll或HelloWorld.so,這之後會提到。
2. Compile HelloWorld.java,用javac指令。
3. 產生一個 native method 的 header file。
指令: javah -jni HelloWorld
這裡,我們用javah可以產生 .h 檔,接著就是實作這個 .h 檔的 .cpp,然後就可以 compile 成 .dll 或 .so 了。cpp 的範例如下:
01.
#include <jni.h>
02.
#include <stdio.h>
03.
#include "HelloWorld.h"
04.
05.
JNIEXPORT
void
JNICALL
06.
Java_HelloWorld_print(JNIEnv *env, jobject obj)
07.
{
08.
printf
(
"Hello World!\n"
);
09.
return
;
10.
}
記得專案的設定中需指定好 jni.h 的目錄。如果是用VS系列的話,專案新增時選DLL版本,compile過後就會輸出HelloWorld.dll。
4. 最後,將HelloWorld.class跟HelloWorld.dll放在一起後,執行 java HelloWorld 就可以看到結果囉。
註: HelloWorld.dll 其實也不一定要跟 HelloWorld.class 放在一起,有個環境變數叫LD_LIBRARY_PATH,它便是用來設定 native library path。還有一種方式,就是利用java指令來指定路徑。下方是將路徑設為當前目錄。
java -Djava.library.path=. HelloWorld
C call Java
這段是參考[1]的第七章與其他收集的資料。
首先,我們用java寫一個 class Prog,請他印出些字,然後用javac去compile出 .class。
1.
public
class
Prog {
2.
public
static
void
main(String[] args) {
3.
System.out.println(
"Hello World "
+ args[
0
]);
4.
}
5.
}
基本上這段程式是可以直接用java指令去執行的,但我們的目標是要產生一個 .c 檔來呼叫它,其流程大致如下:
- 喚醒 Java VM
- 載入指定class path下的所有 .class 們
- 找到你想要執行的class及其 method ID。
- 呼叫 method。
對照第七章的範例來看的話,步驟一加二的程式如下。舊版的JNI 有提供JNI_GetDefaultJavaVMInitArgs,新版的還是請大家自己乖乖指定好 class path 跟其他資訊。JNI_CreateJavaVM 會提供兩個重要的指標,jvm 是指向一個新開的JavaVM,env則是待會讓你用來對java class做怪怪事的介面。
01.
JavaVMInitArgs vm_args;
02.
JavaVMOption options[1];
03.
options[0].optionString =
04.
"-Djava.class.path="
USER_CLASSPATH;
05.
vm_args.version = 0x00010002;
06.
vm_args.options = options;
07.
vm_args.nOptions = 1;
08.
vm_args.ignoreUnrecognized = JNI_TRUE;
09.
/* Create the Java VM */
10.
res = JNI_CreateJavaVM(&jvm, (
void
**)&env, &vm_args);
步驟三加四如下。
01.
cls = (*env)->FindClass(env,
"Prog"
);
02.
if
(cls == NULL) {
03.
goto
destroy;
04.
}
05.
mid = (*env)->GetStaticMethodID(env, cls,
"main"
,
06.
"([Ljava/lang/String;)V"
);
07.
if
(mid == NULL) {
08.
goto
destroy;
09.
}
10.
jstr = (*env)->NewStringUTF(env,
" from C!"
);
11.
if
(jstr == NULL) {
12.
goto
destroy;
13.
}
14.
stringClass = (*env)->FindClass(env,
"java/lang/String"
);
15.
args = (*env)->NewObjectArray(env, 1, stringClass, jstr);
16.
if
(args == NULL) {
17.
goto
destroy;
18.
}
19.
(*env)->CallStaticVoidMethod(env, cls, mid, args);
比較可能看不懂的地方是在GetStaticMethodID的第四個參數,這種詭異的寫法稱為JNI signature,當我們呼叫 Java method 時需要寫一些 signature,用來檢查你指定的型態是否與該 method 一樣。舉些例子好了。
Java constructor:
1.
String s
對應到的signature為:
1.
(Ljava/lang/String;)V
Java method:
1.
String toString()
對應到的signature為:
1.
()Ljava/lang/String;
Java method:
1.
long
myMethod(
int
n, String s,
int
[] arr)
對應到的signature為:
1.
(ILjava/lang/String;[I)J
看了那麼多一定還看不懂對吧?基本上,一個函式會有參數值與回傳值,signature 的規則便是先用 () 描述參數值型態,後面再接回傳值型態。比較特別的,是如果有用到某個 package,除了要打出 package path 外還要加個分號!
下面列出其對應的所有規則,比較一下就可以知道了。
- B=byte
- C=char
- D=double
- F=float
- I=int
- J=long
- S=short
- V=void
- Z=boolean
- Lfully-qualified-class=fully qualified class
- [type=array of type>
- (argument types)return type=method type. If no arguments, use empty argument types: (). If return type is void (or constructor) use (argument types)V.
如果 method signature 不對的話,還會出現類似以下的錯誤訊息。
01.
#
02.
# An unexpected error has been detected by Java Runtime Environment:
03.
#
04.
# Internal Error (sharedRuntime.cpp:552), pid=7304, tid=2492
05.
# Error: guarantee(cb != 0,"exception happened outside interpreter, nmethods and vtable stubs (1)")
06.
#
07.
# Java VM: Java HotSpot(TM) Client VM (10.0-b19 mixed mode, sharing windows-x86)
08.
# If you would like to submit a bug report, please visit:
10.
#
最後,你可能會想問:我要如何知道一個 Java class 裡的所有 method 到底需要哪幾種 signature ? 除了自己一個個看以外,J2SDK 有提供神奇指令來幫助我們!
javap -s -p classname
Reference:
[1] Java Native Interface: Programmer's Guide and Specification
[2] Invocation API of JNI Enhancement
[3] Creating a JVM from a C Program
[4] JNI Spec
留言列表