利用JNI invocation API在應用程式裡啟動JVM

 Sat, 22 Mar 2008 19:01:28 +0800

之前試做的專案,為了不想讓使用者在進入前看到console視窗跳出來影響觀感,所以用ShellExecute配合SW_HIDE參數呼叫java.exe來執行java的程式。測試的結果,在一些較舊的XP機器上,還是會跑出console....(也許是因為我把console預設值改成全螢幕的關係)找了一下資料,發現可以用JNI的invocation api來實現。

接下來就動手試了一下。

經過幾次錯誤嘗試及上網找資料,大致上有幾點要注意:

  1. jvm.dll的位置很重要,因為它會依賴自己在目錄中的位置來尋找相關的lib(jar)、dll檔案的位置。jvm.dll位置不對的時候,連jvm都無法啟動。
  2. 呼叫類別的公開靜態方法(通常就是main啦)時,要先取得這個方法的ID,這時需要用到一個叫做method signature的資訊,用來表達叫用方法參數結構,通常是 ([Ljava/lang/String;)V 。這個資訊,可以也用binary editor直接打開class檔案找到。

要符合以上第一點要求,才能順利啟動jvm。一般透過lib使用dll的方法,在這個時候有可能會碰到問題,windows作業系統會依照一定的規則來尋找dll檔案,包括執行檔所在目錄、PATH環境變數裡面的路徑、windows及system路徑等,但是這樣找到的jvm.dll不一定合乎要求。例如說我想把jre目錄放在執行檔的次目錄裡面,讓執行檔包含次目錄可以隨意拷貝到任何機器中,只要相對路徑維持不變,就可以啟動jvm的話,就可能會出問題。所以後來實現的方式,是使用LoadLibrary函數。

Method Signature不對的話,則會出現像以下的錯誤資訊:

#
# An unexpected error has been detected by Java Runtime Environment:
#
#  Internal Error (sharedRuntime.cpp:552), pid=7304, tid=2492
#  Error: guarantee(cb != 0,"exception happened outside interpreter, nmethods and vtable stubs (1)")
#
# Java VM: Java HotSpot(TM) Client VM (10.0-b19 mixed mode, sharing windows-x86)
# If you would like to submit a bug report, please visit:
#   http://java.sun.com/webapps/bugreport/crash.jsp
#

大概就在這些點上碰到一些問題。其實在網路上有不少範例,例如:

官方文件:http://java.sun.com/j2se/1.4.2/docs/guide/jni/spec/invocation.html

此外,在google上面用jni innvocation api關鍵字就可找到很多資訊的。

使用LoadLibrary來載入jvm.dll另外有一些有用的tips:

宣告JNI_CreateJavaVM函數的typedef來載入這個函數,可以用:

typedef jint (JNICALL* JNI_CreateJavaVM_)(JavaVM**,void**,void*);

接下來就用這個typedef來獲得JNI_CreateJavaVM方法的參考。例如:

JNI_CreateJavaVM_ JNI_CreateJavaVM0 = (JNI_CreateJavaVM_)GetProcAddress(handle,"JNI_CreateJavaVM");

接下來就可以用JNI_CreateJavaVM0來呼叫定義在jvm.dll裡面的JNI_CreateJavaVM函數:

int ret = 0;
JavaVM *jvm;
JNIEnv *env;
JavaVMInitArgs vm_args;
JavaVMOption options[2];
options[0].optionString = "-Djava.class.path=.jrelibswt.jar;.";
options[1].optionString = "index.html";
vm_args.options = options;
vm_args.nOptions = 2;
vm_args.ignoreUnrecognized = JNI_TRUE;
vm_args.version = JNI_VERSION_1_6;
ret = (JNI_CreateJavaVM0)(&jvm, (void**)&env, &vm_args);

接下來的事情,都靠env這個變數來完成,就不多說了,應該不困難。