分享
 
 
 

如何用C++自行加载Java虚拟机

王朝java/jsp·作者佚名  2006-01-31
窄屏简体版  字體: |||超大  

如何用C++自行加载Java虚拟机为什么要自己写C++程序加载Java虚拟机

通常情况下,我们完成了Java类的开发后,都是通过脚本调用Java的java.exe或者javaw.exe来启动Java虚拟机的,但在某些情况下,我们需要用C++程序来自行加载Java虚拟机运行我们的java程序,例如:

1、 在windows上面运行时,你不想让那个恼人的DOS窗口出现,DOS窗口除了难看以外,对于服务器程序,有时这个DOS窗口是危险的,比如某个管理员不小心用鼠标点了一下DOS窗口的那个叉,或者为了拷贝什么东西按了一下Ctrl-C,都会把整个Java程序给终止掉。

2、 你想把DOS窗口里面显示的内容记录到文件里面,以便你以后分析。这是个很有用的功能。通常很多异常堆栈,和系统发生的异常消息都是在DOS窗口中输出的,有时输出内容太多时,DOS窗口以前显示的内容就看不见了,DOS窗口关掉后,你也没法再看到以前显示的内容了。

3、 在windows下面,你想把java程序做成一个后台服务,就像tomcat那样,这样就可以让windows操作系统帮你管理java程序的启动和停止了。

4、 你在开发象IE这样的程序,在必要的时候才启动虚拟机运行一段Java程序

本文就教你如何做到这一点。

C++加载虚拟机的两种方法比较

加载虚拟机可以采用以下两种方法:

1、 用创建子进程的方法调用java.exe来启动,在windows下面可以用CreateProcess方法,在Linux下面可以用execvp方法,调用的方式和用脚本调用差不多,这种方法比较简单我就不详细介绍了,详细的方式可以参考开源项目 Java Service Wrapper。用创建子进程的方式用以下优点:

1) 开发简单,兼容性好,对于java虚拟机的不同版本调用方式都是一样的

2) 创建出来的java进程和C++程序的进程分离,不会相互影响,你不会因为C程序出现什么内存错误崩溃导致java进程也一起完蛋(当然分离进程也带来了其他问题,详见我后面的分析)。

但是我不太喜欢这种方法,因为有以下缺点:

1) 生成分离的java进程后,在系统里会看到另外一个java.exe的进程,当启动了很多这样的进程时,你会看到很多名称为java的进程,你无法区分哪个是哪个。

2) 生成了分离的Java 进程后,你就不好对它进行控制,比如说要关闭它,如果直接kill掉java进程,可能java进程里面一些关闭时需要做的清理工作还来不及做。如果想让java程序执行清理工作再退出,你就需要建立和java程序之间的通讯机制,以向java程序发出退出的消息,这通常还需要在java程序里面加载专门的类,以及需要java程序的开发人员进行相关的接口开发工作。

2、 加载java虚拟机的另外一种方法是加载jvm的动态库,并通过动态库的接口来在本进程内启动java虚拟机。我更加喜欢这种方法,因为这种方法可以带来很多好处:

1) 你可以更好地对java虚拟机进行有效地控制,java程序不需要加载专门的类库,你只需要调用不同的java类方法就可以控制java程序的启动、停止等操作

2) 不会产生额外的java进程,在系统进程中进程名称是你C++主程序的名字,你可以在系统进程列表中很清楚地区分每个进程

当然这种方法也有缺点,但我认为不是大问题:

1) 由于java虚拟机在进程内部加载,如果主程序写得不好而崩溃会导致java程序也一起终止

2) 动态库接口得开发要稍微难一点,需要你了解jvm动态库的接口,不过待会儿你就会看到,这也不是很困难的事情

3) jdk不同版本的接口稍微有一点区别,以后可能也会发生变化,你可能得对不同的jdk版本修改一下C程序,但相对来说jvm的兼容性应该是可以信任的

加载jvm动态库的方法介绍下面就详细介绍一下用C++程序加载动态库的方法,我将分别针对windows平台和linux/unix平台分别进行介绍:

windows平台的加载方法windows平台下jdk的java虚拟机动态库为jvm.dll,位于:

%JAVA_HOME%/jre/bin/client/jvm.dll

%JAVA_HOME%/jre/bin/server/jvm.dll

Jvm动态库有client和server两个版本,分别针对桌面应用和服务器应用做了相应的优化,client版本加载速度较快,server版本加载速度较慢但运行起来较快。

虚拟机加载可按照以下步骤进行:

1) 装载jvm动态库

2) 查找JNI_CreateJavaVM接口

3) 设置JVM启动参数,调用JNI_CreateJavaVM接口启动虚拟机

4) 查找启动类,设置命令行参数,设置窗口输出重导向文件

5) 调用启动类的启动方法启动java程序

6) 要停止Java程序运行时,调用java类的停止方法即可

下面的示例代码就说明了一个简单的加载过程

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

bool setStream(JNIEnv *env, const char* pszFileName, const char* pszMethod);

//启动java虚拟机方法

bool startJvm()

{

//jvm动态库的路径

const char szJvmPath[] = "c:\\jdk1.5.0_01\\jre\\bin\\server\\jvm.dll";

//java 虚拟机的启动参数,每个参数写一项,不能合在一起写

int nOptionCount = 2;

JavaVMOption options[2];

options[1].optionString = "-Xmx256M";

//设置classpath

options[0].optionString = "-Djava.class.path=./Test.jar;./Test1.jar";

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[] = "test/JTest";

//启动方法,通常是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;

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;

for (int i=0; i<nParamCount; i++)

{

jstr = env->NewStringUTF(szParams[i]);

if (jstr == 0) {

printf("分配String失败\n");

if (env->ExceptionOccurred()) {

env->ExceptionDescribe();

env->ExceptionClear();

}

return false;

}

env->SetObjectArrayElement(args, i, jstr);

if (env->ExceptionCheck() == JNI_TRUE)

{

printf("设置参数失败\n");

if (env->ExceptionOccurred()) {

env->ExceptionDescribe();

env->ExceptionClear();

}

return false;

}

}

//调用启动类的启动方法启动Java程序

env->CallStaticVoidMethod(serviceClass, mid, parameterArray);

if (env->ExceptionCheck() == JNI_TRUE)

{

env->ExceptionDescribe();

env->ExceptionClear();

FreeLibrary(jvmDll);

return false;

}

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;

}

}

Linux平台的加载方法Linux平台下的虚拟机的加载方法跟windows平台基本一致,不同的地方仅在于加载动态库的方法不同,在linux平台的JDK中,jvm动态库所在的路径为$JAVA_HOME/jre/lib/i386/client/libjvm.so

$JAVA_HOME/jre/lib/i386/server/libjvm.so

在Linux下装载动态库的示例代码如下:

#include <jni.h>

#include <stdlib.h>

#include <dlfcn.h>

typedef void* (*JNICREATEPROC)(JavaVM **, JNIEnv **, void *);

bool startJVM() {

JNIEnv *env;

JavaVM *jvm;

jclass cls;

jmethodID mid;

jobjectArray args;

//jvm动态库的路径

const char szJvmPath[] = "/usr/jdk1.5.0_01/jre/lib/i386/server/libjvm.so";

//启动类

const char szStartClass[] = "test/JTest";

//启动方法

const char szStartMethod[] = "main";

//Java启动参数

int nOptionCount = 2;

JavaVMOption options[2];

options[1].optionString = "-Xmx256M";

options[0].optionString = "-Djava.class.path=./Test.jar;./Test1.jar";

JavaVMInitArgs vm_args;

vm_args.version = JNI_VERSION_1_4;

vm_args.options = options;

vm_args.nOptions = nOptionCount;

vm_args.ignoreUnrecognized = JNI_TRUE;

//命令行参数

int nParamCount = 2;

const char *szParams[2] = {"arg1","arg2"};

//装载动态库

void* lib_handle = 0;

lib_handle = dlopen(szJvmPath, RTLD_NOW);

if (!lib_handle)

{

fprintf(stderr, "dlopen failed\n");

return false;

}

JNICREATEPROC lib_func = 0;

//查找JNI_CreateJavaVM过程

lib_func = (JNICREATEPROC)dlsym(lib_handle, "JNI_CreateJavaVM");

lib_func(&jvm,&env,&vm_args);

//后面与windows平台处理流程完全一样

cls = env->FindClass(szStartClass);

if (cls == 0) {

fprintf(stderr, "Can't find class:%s\n",szStartClass);

if (env->ExceptionOccurred()) {

env->ExceptionDescribe();

}

return false;

}

mid = env->GetStaticMethodID(cls, szStartMethod,"([Ljava/lang/String;)V");

if (mid == 0) {

fprintf(stderr, "Can't find Method:%s\n",szStartMethod);

if (env->ExceptionOccurred()) {

env->ExceptionDescribe();

}

return false;

}

jclass stringClass = env->FindClass("java/lang/String");

if (env->ExceptionCheck() == JNI_TRUE || stringClass == NULL)

{

fprintf(stderr, "find String class error");

if (env->ExceptionOccurred()) {

env->ExceptionDescribe();

}

return false;

}

args = env->NewObjectArray(nParamCount, stringClass, NULL);

if (args == 0) {

fprintf(stderr, "Out of memory\n");

if (env->ExceptionOccurred()) {

env->ExceptionDescribe();

}

return false;

}

jstring jstr;

for (int i=0; i<nParamCount; i++)

{

jstr = env->NewStringUTF(szParams[i]);

if (jstr == 0) {

fprintf(stderr, "Out of memory\n");

if (env->ExceptionOccurred()) {

env->ExceptionDescribe();

}

jvm->DestroyJavaVM();

return false;

}

env->SetObjectArrayElement(args, i, jstr);

if (env->ExceptionCheck() == JNI_TRUE)

{

fprintf(stderr, "set param error\n");

if (env->ExceptionOccurred()) {

env->ExceptionDescribe();

}

jvm->DestroyJavaVM();

return -1;

}

}

env->CallStaticVoidMethod(cls, mid, args);

if (env->ExceptionOccurred()) {

env->ExceptionDescribe();

return false;

}

return true;

}

参考资料1、 用创建子进程方式启动Java虚拟机的具体方法,可参考开源项目 Java Service Wrapper,http://wrapper.tanukisoftware.org

2、 关于jvm动态库JNI方法的具体说明,参加SUN网站关于JNI的说明http://java.sun.com/j2se/1.4.2/docs/guide/jni/

版权声明

本文为fita个人原创,未经本人允许不得转载、摘抄

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有