將Java代碼打包爲exe文件

1       基本信息
摘要:
          現在有很多的工具將Java代碼打包爲exe文件,執行時不需要再編寫批處理文件,或者在命令行輸入長長的classpath信息,爲用戶使用提供了很大的方便。這也是很多商業軟件常常使用的方法。
作者:晏斐 
2       將Java代碼打包爲exe文件
       現在有很多的工具將Java代碼打包爲exe文件,執行時不需要再編寫批處理文件,或者在命令行輸入長長的classpath信息,爲用戶使用提供了很大的方便。這也是很多商業軟件常常使用的方法。
       將Java代碼打包爲exe文件,一般需要兩個步驟:
1.       編寫本地代碼,創建虛擬機,加載並執行Main Class。
2.       將Java代碼打包爲jar文件,並與本地代碼exe文件合併。
下面的代碼,會加載jvm.dll,並調用JNI_CreateJavaVM導出函數創建Java虛擬機,得到JNIEnv指針,然後調用FindClass查找Main Class,之後調用GetStaticMethodID方法得到main方法,並執行main方法。代碼如下:

#include <windows.h>
#include <jni.h>
//#pragma comment( linker, "/subsystem:"console" /entry:"mainCRTStartup"" ) 
#pragma comment( linker, "/subsystem:"windows" /entry:"WinMainCRTStartup"" ) 

typedef jint (JNICALL *JNICREATEPROC)(JavaVM **, 
void **, void *);
bool setStream(JNIEnv *env, const char* pszFileName, const char* pszMethod);
//啓動java虛擬機方法

//bool main(int argc,char *argv[])
int WINAPI WinMain (HINSTANCE hInst, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow)
...{
    
//jvm動態庫的路徑
    const char szJvmPath[] = "d:/jdk1.5.0_07/jre/bin/server/jvm.dll";
    
    
//java 虛擬機的啓動參數,每個參數寫一項,不能合在一起寫
    int nOptionCount = 2;
    JavaVMOption options[2];
    options[1].optionString = "-Xmx256M";
    
    
//設置classpath
    options[0].optionString = "-Djava.class.path=./Test.exe";
    
    JavaVMInitArgs vm_args;
    vm_args.version = JNI_VERSION_1_4;
    vm_args.options = options;
    vm_args.nOptions = nOptionCount;
    vm_args.ignoreUnrecognized = JNI_TRUE;
    
    
//啓動類,注意分割符是/,例如啓動類test.JTest應該寫成 test/JTest
    const char szStartClass[] = "com/primeton/test/TestClass";
    
    
//啓動方法,通常是main函數,你也可以設定成其他函數
    const char szStartMethod[] = "main";
    
    
//重導向文件
    const char szStdoutFileName[] = "stdout.txt";
    
const char szStderrFileName[] = "stderr.txt";
    
    
//java程序的命令行參數
    int nParamCount = 2;
    
const char *szParams[2] = ...{"arg1","arg2"};
    
    
//加載JVM
    HINSTANCE jvmDll = LoadLibrary(szJvmPath);
    
if (jvmDll == NULL)
    
...{
        printf("
加載JVM動態庫錯誤。%l", ::GetLastError());
        
return false;
    }
    
    
//查找JNI_CreateJavaVM過程。
    JNICREATEPROC jvmCreateProc = (JNICREATEPROC)GetProcAddress(jvmDll, "JNI_CreateJavaVM");
    
if (jvmCreateProc == NULL)
    
...{
        FreeLibrary(jvmDll);
        printf("
查找JNI_CreateJavaVM過程錯誤。%l", ::GetLastError());
        
return false;
    }
    
    
//創建JVM
    JNIEnv *env;
    JavaVM *jvm;
    jint r = (jvmCreateProc)(&jvm, (
void **)&env, &vm_args);
    
if (r < 0 || jvm == NULL || env == NULL)
    
...{
        FreeLibrary(jvmDll);
        printf( "
創建JVM發生錯誤。");
        
return false;
    }
    
    
//重導向stdout, stderr到輸出文件
    if (!setStream(env, szStdoutFileName, "setOut"))
    
...{
        printf("
設置stdout輸出文件失敗");
        
return false;
    }
    
    
if (!setStream(env, szStderrFileName, "setErr"))
    
...{
        printf("
設置stderr輸出文件失敗");
        
return false;
    }
    
    
//加載啓動類。
    jclass serviceClass = env->FindClass(szStartClass);
    
if (env->ExceptionCheck() == JNI_TRUE || serviceClass == NULL)
    
...{
        env->ExceptionDescribe();
        env->ExceptionClear();
        FreeLibrary(jvmDll);
        printf("
加載啓動類失敗。");
        
return false;
    }
    
    
//啓動方法
    jmethodID mid = env->GetStaticMethodID(serviceClass, szStartMethod , "([Ljava/lang/String;)V");
    
if (env->ExceptionCheck() == JNI_TRUE || mid == NULL)
    
...{
        env->ExceptionDescribe();
        env->ExceptionClear();
        FreeLibrary(jvmDll);
        printf("
查找啓動方法失敗。");
        
return false;
    }
    
    
    
    
//查找String類。
    jclass stringClass = env->FindClass("java/lang/String");
    
if (env->ExceptionCheck() == JNI_TRUE || stringClass == NULL)
    
...{
        env->ExceptionDescribe();
        env->ExceptionClear();
        FreeLibrary(jvmDll);
        
        printf("
查找String類失敗。");
        
return false;
    }
    
    
    
    jstring jstr;
    jobjectArray args = 0;
    
    args = env->NewObjectArray(2, stringClass, 0);
    
for (int i=0; i<nParamCount; i++)
    
...{
        jstr = env->NewStringUTF(szParams[i]);
        
if (jstr == 0) ...{
            printf("
分配String失敗 ");
            
if (env->ExceptionOccurred()) ...{
                env->ExceptionDescribe();
                env->ExceptionClear();
            }
            
            
return false;
        }
        
        env->SetObjectArrayElement(args, i, jstr);
        
if (env->ExceptionCheck() == JNI_TRUE)
        
...{
            printf("
設置參數失敗 ");
            
if (env->ExceptionOccurred()) ...{
                env->ExceptionDescribe();
                env->ExceptionClear();
            }
            
return false;
        }
    }
    
    
    
    
//調用啓動類的啓動方法啓動Java程序
    //env->CallStaticVoidMethod(serviceClass, mid, parameterArray);
    env->CallStaticVoidMethod(serviceClass, mid, args);
    
    
if (env->ExceptionCheck() == JNI_TRUE)
    
...{
        env->ExceptionDescribe();
        env->ExceptionClear();
        FreeLibrary(jvmDll);
        
return false;
    }
    
    MSG  msg ;
    
while (GetMessage (&msg, NULL, 0, 0))
    
...{
        TranslateMessage (&msg) ;
        DispatchMessage (&msg) ;
    }
    
return true;
    
}

//設置輸出流的方法

bool setStream(JNIEnv *env, const char* pszFileName, const char* pszMethod)
...{
    
int pBufferSize = 1024;
    
char* pBuffer = new char[pBufferSize];
    
    
//創建字符串對象。
    jstring pathString = env->NewStringUTF(pszFileName);
    
if (env->ExceptionCheck() == JNI_TRUE || pathString == NULL)
    
...{
        env->ExceptionDescribe();
        env->ExceptionClear();
        printf("
創建字符串失敗。");
        
return false;
    }
    
    
//查找FileOutputStream類。
    jclass fileOutputStreamClass = env->FindClass("java/io/FileOutputStream");
    
if (env->ExceptionCheck() == JNI_TRUE || fileOutputStreamClass == NULL)
    
...{
        env->ExceptionDescribe();
        env->ExceptionClear();
        printf("
查找FileOutputStream類失敗。");
        
return false;
    }
    
    
//查找FileOutputStream類構造方法。
    jmethodID fileOutputStreamConstructor = env->GetMethodID(fileOutputStreamClass, "<init>", "(Ljava/lang/String;)V");
    
if (env->ExceptionCheck() == JNI_TRUE || fileOutputStreamConstructor == NULL)
    
...{
        env->ExceptionDescribe();
        env->ExceptionClear();
        printf("
查找FileOutputStream類構造方法失敗。");
        
return false;
    }
    
    
//創建FileOutputStream類的對象。
    jobject fileOutputStream = env->NewObject(fileOutputStreamClass, fileOutputStreamConstructor, pathString);
    
if (env->ExceptionCheck() == JNI_TRUE || fileOutputStream == NULL)
    
...{
        env->ExceptionDescribe();
        env->ExceptionClear();
        printf("
創建FileOutputStream類的對象失敗。");
        
return false;
    }
    
    
//查找PrintStream類。
    jclass printStreamClass = env->FindClass("java/io/PrintStream");
    
if (env->ExceptionCheck() == JNI_TRUE || printStreamClass == NULL)
    
...{
        env->ExceptionDescribe();
        env->ExceptionClear();
        
        printf("
查找PrintStream類失敗。");
        
return false;
    }
    
    
//查找PrintStream類構造方法。
    jmethodID printStreamConstructor = env->GetMethodID(printStreamClass, "<init>", "(Ljava/io/OutputStream;)V");
    
if (env->ExceptionCheck() == JNI_TRUE || printStreamConstructor == NULL)
    
...{
        env->ExceptionDescribe();
        env->ExceptionClear();
        printf("
查找PrintStream類構造方法失敗。");
        
return false;
    }
    
    
//創建PrintStream類的對象。
    jobject printStream = env->NewObject(printStreamClass, printStreamConstructor, fileOutputStream);
    
if (env->ExceptionCheck() == JNI_TRUE || printStream == NULL)
    
...{
        env->ExceptionDescribe();
        env->ExceptionClear();
        printf("
創建PrintStream類的對象失敗。");
        
return false;
    }
    
    
//查找System類。
    jclass systemClass = env->FindClass("java/lang/System");
    
if (env->ExceptionCheck() == JNI_TRUE || systemClass == NULL)
    
...{
        env->ExceptionDescribe();
        env->ExceptionClear();
        printf( "
查找System類失敗。");
        
return false;
    }
    
    
//查找System類設置方法。
    jmethodID setStreamMethod = env->GetStaticMethodID(systemClass, pszMethod, "(Ljava/io/PrintStream;)V");
    
if (env->ExceptionCheck() == JNI_TRUE || setStreamMethod == NULL)
    
...{
        env->ExceptionDescribe();
        env->ExceptionClear();
        printf("
查找System類設置方法失敗。");
        
return false;
    }
    
    
//設置System類的流。
    env->CallStaticVoidMethod(systemClass, setStreamMethod, printStream);
    
if (env->ExceptionCheck() == JNI_TRUE)
    
...{
        env->ExceptionDescribe();
        env->ExceptionClear();
        printf("
設置System類的流失敗。");
        
return false;
    }
    
    
return true;
}
 
第二步,將Java文件打包爲exe文件,也很簡單。在Dos提示符下執行copy命令:
C:/>copy test.exe+test.jar test.exe
其實,就是將Java打包文件追加到exe文件尾部。打開文件屬性對話框,可看到有“壓縮文件”屬性頁。老牌的JBuilder.exe開發工具編譯生成的exe文件即採用如下方式生成。
後記:大家在使用Eclipse 3.2和Eclipse 3.3時,在任務管理器中會看到二者的不同。Eclipse 3.2是先啓動Eclipse.exe文件,然後由Eclipse.exe啓動Javaw.exe文件來創建虛擬機。
Eclipse 3.2在任務管理器中顯示爲Eclipse.exe和javaw.exe兩個進程。
Eclipse 3.3在任務管理器中顯示爲Eclipse.exe一個進程。
從上面可以看出,Eclipse 3.2和Eclipse 3.3採用了不同的虛擬機加載方式。
 
Eclipse 3.2採用創建子進程的方法調用javaw.exe來啓動,在windows下面可以用CreateProcess方法,此種方法較簡單,具體可參見Eclipse源碼。
 
Eclipse 3.3加載java虛擬機的另外一種方法是加載jvm的動態庫,並通過動態庫的接口來在本進程內啓動java虛擬機。本文開頭即採用的第二種方法。
發佈了35 篇原創文章 · 獲贊 4 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章