小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

打造個性化Java啟動器

 9loong 2012-03-28


2008-04-03 11:01:29|  

主要內容:

________________________

 一、Java程序的啟動過程

 二、Windows平臺的啟動器

 三、配置和使用

________________________

對于普通用戶來說,Java最讓人不習慣的是程序的啟動過程;即使對于富有經(jīng)驗的開發(fā)者,為了用默認的裝載器啟動Java程序,不得不編寫大量批命令、腳本文件,不得不在命令行環(huán)境下進行大量的復制/粘貼操作,也很容易出現(xiàn)誤操作。

用慣了Windows方便快捷的GUI,人們早就習慣了通過雙擊運行程序的方式。對于 Java程序,要實現(xiàn)這個本機啟動功能就必須編寫定制的啟動器。用定制啟動器啟動Java程序不僅方便了最終用戶,而且使軟件作品看起來更專業(yè)。本文就以 Windows平臺為例,介紹如何構造Java定制啟動器。

一、Java程序的啟動過程

和C/C++程序相比,Java程序的啟動過程要復雜得多,這主要是因為Java是一種編譯成中間語言(字節(jié)碼)后解釋執(zhí)行的語言。啟動和關閉Java程序需要多個步驟才能完成,如圖一所示。

圖一

Java程序可以由任何本機運行的程序調用執(zhí)行。所謂Java啟動器,就是一個專門用來啟動 Java程序的本機執(zhí)行程序。最常見的啟動器是Sun在Java Runtime Environment的/bin目錄中提供的啟動器,就Windows平臺而言,它們是java.exe和javaw.exe。前者運行時打開兩個窗 口:一個是接收System.out/err和啟動器輸出的控制臺窗口,另一個是Java程序本身的窗口;javaw運行時不打開控制臺窗口。在J2SE /EE平臺中,虛擬機以動態(tài)庫的形式實現(xiàn),也放在/bin目錄下。動態(tài)庫的名字在Windows中是java.dll,在Unix中是java.so。所 謂“裝入虛擬機”,就是指裝入這個動態(tài)庫。

提供給VM的參數(shù)可以通過兩種方式指定,或者是在啟動器的命令行參數(shù)中指定,或者通過 定義相應的環(huán)境變量指定。只有一個參數(shù)例外——要啟動的類的名稱只能在啟動器的命令行參數(shù)中指定。雖然指定方式的多樣性為人們各取所需帶來了方便,但不可 否認地,它也正是許多混亂的根源。使用定制啟動器能夠完全避免這方面的問題。

當VM結束啟動類的main()方法的運行,啟動器調用destroy()方法釋放各 種資源并退出。應當注意的是,VM一旦開始運行,我們就不能再卸載它。對于Java啟動器來說,能否關閉VM無關緊要,因為啟動器會隨著Java程序的退 出而退出;然而,對于嵌入了VM的本機應用,例如瀏覽器,這意味著有一塊內存被永久性地占用,不能再收回。

二、Windows平臺的啟動器

搞清楚了Java程序的啟動過程,我們就可以開始編寫啟動器的代碼。下面這個啟動器用C++寫成,適合于所有Windows平臺。

// Windows平臺下的Java程序啟動器
// 適用于1.2或更高版本的VM
#include <windows.h>
#include <jni.h>
#include <string>
using namespace std;

void vShowError(string sErrorMessage);
void vShowLastError(string sErrorMessage);
void vDestroyVM(JNIEnv *env, JavaVM *jvm);
void vAddOption(string& sName);

JavaVMOption* vm_options;
int mctOptions = 0;
int mctOptionCapacity = 0;
boolean GetApplicationHome(char *buf, jint sz);
typedef jint (CALLBACK *CreateJavaVM)(JavaVM **pvm, JNIEnv **penv, void *args);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow){
    JNIEnv *env;
    JavaVM *jvm;
    jint jintVMStartupReturnValue;
    jclass jclassStartup;
    jmethodID midStartup;

    // 確定各種文件所在的路徑
    // -應用的主目錄
    char home[2000];
    if (!GetApplicationHome(home, sizeof(home))) {
        vShowError("不能確定應用的主目錄。");
        return 0;
    }
    string sAppHome(home);
    string sOption_AppHome = "-Dapplication.home=" + sAppHome;
    string sJREPath = sAppHome + "\\jre";
    // -VM路徑
    string sRuntimePath = sJREPath + "\\bin\\classic\\";
    string sJVMpath = sRuntimePath + "jvm.dll";
    // -啟動路徑
    string sBootPath = sJREPath + "\\lib";
    string sOption_BootPath = "-Dsun.boot.class.path=" + sBootPath;
    // -CLASSPATH
    string sClassPath = sAppHome + "\\classes";
    string sOption_ClassPath = "-Djava.class.path=" + sClassPath;
    // 設置VM參數(shù)
    // vAddOption(string("-verbose"));
    vAddOption(sOption_ClassPath);
    vAddOption(sOption_AppHome);
    // VM初始化參數(shù)
    JavaVMInitArgs vm_args;
    vm_args.version = 0x00010002;
    vm_args.options = vm_options;
    vm_args.nOptions = mctOptions;
    vm_args.ignoreUnrecognized = JNI_TRUE;
    // 裝入JVM庫
    HINSTANCE hJVM = LoadLibrary(sJVMpath.c_str());
    if( hJVM == NULL ){
        vShowLastError("不能從下面的路徑裝入JVM:" + sJVMpath);
        return 0;
    }
    // 啟動1.2/3/4 VM
    CreateJavaVM lpfnCreateJavaVM = (CreateJavaVM) GetProcAddress(hJVM, "JNI_CreateJavaVM");
    jintVMStartupReturnValue = (*lpfnCreateJavaVM) (&jvm, &env, &vm_args);
    // 是否成功?
    if (jintVMStartupReturnValue < 0) {
        string sErrorMessage = "創(chuàng)建VM失敗。";
        vShowError(sErrorMessage);
        vDestroyVM(env, jvm);
        return 0;
    }
    // 要啟動的類
    string sStartupClass = "javabunny/JavaBunny";
    // 注意句點符號已經(jīng)被轉換成斜杠
    jclassStartup = env->FindClass(sStartupClass.c_str());
    if (jclassStartup == NULL) {
        string sErrorMessage ="找不到啟動類[" +sStartupClass + "]";
        vShowError(sErrorMessage);
        vDestroyVM(env, jvm);
        return 0;
    }
    // 要啟動的方法
    string sStartupMethod_Identifier = "main";
    string sStartupMethod_TypeDescriptor ="([Ljava/lang/String;)V";
    midStartup = env->GetStaticMethodID(jclassStartup,
    sStartupMethod_Identifier.c_str(),
    sStartupMethod_TypeDescriptor.c_str());
    if (midStartup == NULL) {
        string sErrorMessage = "找不到啟動方法["+ sStartupClass + "."+ sStartupMethod_Identifier
        + "],類型描述符是[" + sStartupMethod_TypeDescriptor + "]";
        vShowError(sErrorMessage);
        vDestroyVM(env, jvm);
        return 0;
    }
    // 構造啟動方法的參數(shù)
    jstring jstringExampleArg;
    jclass jclassString;
    jobjectArray jobjectArray_args;
    jstringExampleArg = env->NewStringUTF("string1");
    if (jstringExampleArg == NULL){
        vDestroyVM(env, jvm);
        return 0;
    }
    jclassString = env->FindClass("java/lang/String");
    jobjectArray_args = env->NewObjectArray(1, jclassString, jstringExampleArg);
    if (jobjectArray_args == NULL){
        vDestroyVM(env, jvm);
        return 0;
    }
    // 調用啟動方法啟動Java程序
    env->CallStaticVoidMethod(jclassStartup, midStartup, jobjectArray_args);
    // 在退出之前嘗試分離主線程
    if (jvm->DetachCurrentThread() != 0) {
        vShowError("分離主線程失敗。\n");
    }
    // 只要還有非守護線程,下面的調用將一直被掛起
    jvm->DestroyJavaVM();
    return 0;
}
void vDestroyVM(JNIEnv *env, JavaVM *jvm){
    if (env->ExceptionOccurred()) {
        env->ExceptionDescribe();
    }
    jvm->DestroyJavaVM();
}

void vShowError(string sError) {
    MessageBox(NULL, sError.c_str(), "錯誤", MB_OK);
}

/* 在對話框中顯示錯誤信息,括號內包含
的GetLastError錯誤信息 */
void vShowLastError(string sLocalError) {
    LPVOID lpSystemMsgBuf;
    FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER |
    FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
    NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
    (LPTSTR) &lpSystemMsgBuf, 0, NULL );
    string sSystemError = string((LPTSTR)lpSystemMsgBuf);
    vShowError(sLocalError + " [" + sSystemError + "]");
}

void vAddOption(string& sValue) {
    mctOptions++;
    if (mctOptions >= mctOptionCapacity) {
        if (mctOptionCapacity == 0) {
            mctOptionCapacity = 3;
            vm_options = (JavaVMOption*)malloc(mctOptionCapacity * sizeof(JavaVMOption));
        } else {
            JavaVMOption *tmp;
            mctOptionCapacity *= 2;
            tmp = (JavaVMOption*)malloc(mctOptionCapacity * sizeof(JavaVMOption));
            memcpy(tmp, vm_options, (mctOptions-1) * sizeof(JavaVMOption));
            free(vm_options);
            vm_options = tmp;
        }
    }
    vm_options[mctOptions-1].optionString = (char*)sValue.c_str();
}

/* 如果緩沖區(qū)是"c:\app\bin\java",則把"c:\app"放入buf。*/
jboolean GetApplicationHome(char *buf, jint sz) {
    char *cp;
    GetModuleFileName(0, buf, sz);
    *strrchr(buf, '\\') = '\0';
    if ((cp = strrchr(buf, '\\')) == 0) {
        // 如果應用程序放在驅動器的根目錄下,且不存在bin目錄
        // 會出現(xiàn)這種情形
        buf[0] = '\0';
        return JNI_FALSE;
    }
    return JNI_TRUE;
}

首先,就象大多數(shù)Windows程序一樣,啟動器需要一個WinMain()入 口。與Windows這一特定平臺相關的問題,除了必要的類型轉換(例如對CreateJavaVM()的轉換)之外,另外一個要注意的地方就是裝入VM 的DLL文件。裝入DLL文件最可靠的辦法是顯式地調用LoadLibrary()。裝入JVM之后,就可以利用內核調用 GetProcAddress()獲得CreateJavaVM()的函數(shù)指針,然后調用該指針啟動VM。

在啟動類的標識符中使用的分隔符是斜杠,而不是句點,即我們用 “javabunny/JavaBunny”表示啟動類,而不是用“javabunny.JavaBunny”的形式。這是因為,F(xiàn)indClass() 是一個虛擬機調用,而虛擬機內部用斜杠作為分隔符。隨便說明一下,這個例子把啟動類的名字(和其他一些配置選項)直接寫進了代碼之中(稱為“硬編碼”), 對于提供給最終用戶使用的產品,這種做法有其優(yōu)點;但對于開發(fā)環(huán)境來說,這些值最好拿出來放在某個配置文件中。

Java程序啟動后執(zhí)行的第一個方法稱為啟動方法,通常是main()。本例通過 JNI調用GetStaticMethodID()獲得啟動方法的ID。GetStaticMethodID()要求指定方法的名字(“main”)和方 法的類型描述符(“([Ljava/lang/String;)V”)。這個類型描述符表示方法的參數(shù)是一個字符串的數(shù)組,返回值類型是void。有關類 型描述符的更詳細的說明,請參見JVM相關資料。注意,從這里可以看出,在使用定制啟動器時,Java程序的啟動方法不必一定是static void的main方法,可以用任何方法作為Java程序中第一個執(zhí)行的方法,甚至包括實例方法或構造函數(shù)。

示例程序中最后一個需要注意的地方是jvm->DestroyJavaVM() 調用。從表面看起來,這個語句似乎是程序執(zhí)行后進行清理工作的方法,可有可無。其實不然,如果Java程序是多線程的,在調用這個方法時程序仍舊在運行。 例如,對于一個運行著的Swing程序,如果它的main方法結束,DestroyJavaVM()的執(zhí)行將被阻塞,直至所有非守護線程都執(zhí)行完畢,所以 這行代碼是必不可少的。如果省略這行代碼,則當主線程執(zhí)行完畢,即使其他線程(例如Swing GUI的事件循環(huán))仍舊在運行,整個程序也會立即退出。

三、配置和使用

如前所述,這個啟動器以硬編碼的方式指定了啟動類的名字,但是沒有一個路徑是硬編碼 的。這是定制啟動器的優(yōu)點之一,由于所有的路徑都是相對的,用戶可以把整個Java應用從一個文件夾拖到另一個驅動器(或另一臺機器)的文件夾,程序的運 行不會出現(xiàn)任何問題。本文的啟動器假定JRE總是在應用軟件所在目錄的一個子目錄下,也就是說,JRE應當隨同應用軟件一起發(fā)布。這樣做的好處是使得應用 軟件完全不依賴于用戶的環(huán)境,確保了JRE與應用程序的兼容性。即使用戶系統(tǒng)中原來已經(jīng)有JRE,增加一個額外的JRE也只不過稍微占用了一點磁盤空間, 但卻能有效地保證應用軟件的穩(wěn)定性。

在某些場合,你可能需要將一些配置參數(shù)移出程序,例如放入一個配置文件,特別是在需要頻繁改動啟動方式的開發(fā)階段。建議移出程序之外的配置選項包括:啟動類,類的路徑,某些VM參數(shù),例如“-verbose”。


(###)

    本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內容均由用戶發(fā)布,不代表本站觀點。請注意甄別內容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內容,請點擊一鍵舉報。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多