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.3加载java虚拟机的另外一种方法是加载jvm的动态库,并通过动态库的接口来在本进程内启动java虚拟机。本文开头即采用的第二种方法。