JNI官方规范中文版——如何把一个JVM嵌入到本地程序中 native程序中加载jvm

http://blog.csdn.net/a345017062/article/details/8068936

本章讲述如何把一个JVM嵌入到你的本地程序当中去。一个JVM可以看作就是一个本地库。本地程序可以链接这个库,然后通过“调用接口”(invocation interface)来加载JVM。实际上,JDK中标准的启动器也就是一段简单的链接了JVM的C代码。启动器解析命令、加载JVM、并通过“调用接口”(invocation interface)运行JAVA程序。

7.1 创建JVM

我们用下面这段C代码来加载一个JVM并调用Prog.main方法来演示如何使用调用接口。

public class Prog {

     public static void main(String[] args) {

          System.out.println("Hello World " + args[0]);

     }

 }

下面是启动器:

#include <jni.h>

 

 #define PATH_SEPARATOR ';' /* define it to be ':' on Solaris */

 #define USER_CLASSPATH "." /* where Prog.class is */

 

 main() {

     JNIEnv *env;

     JavaVM *jvm;

     jint res;

     jclass cls;

     jmethodID mid;

     jstring jstr;

     jclass stringClass;

     jobjectArray args;

 

 #ifdef JNI_VERSION_1_2

     JavaVMInitArgs vm_args;

     JavaVMOption options[1];

     options[0].optionString =

         "-Djava.class.path=" USER_CLASSPATH;

     vm_args.version = 0x00010002;

     vm_args.options = options;

     vm_args.nOptions = 1;

     vm_args.ignoreUnrecognized = JNI_TRUE;

     /* Create the Java VM */

     res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);

 #else

     JDK1_1InitArgs vm_args;

     char classpath[1024];

     vm_args.version = 0x00010001;

     JNI_GetDefaultJavaVMInitArgs(&vm_args);

     /* Append USER_CLASSPATH to the default system class path */

     sprintf(classpath, "%s%c%s",

             vm_args.classpath, PATH_SEPARATOR, USER_CLASSPATH);

     vm_args.classpath = classpath;

     /* Create the Java VM */

     res = JNI_CreateJavaVM(&jvm, &env, &vm_args);

 #endif /* JNI_VERSION_1_2 */

 

     if (res < 0) {

         fprintf(stderr, "Can't create Java VM\n");

         exit(1);

     }

     cls = (*env)->FindClass(env, "Prog");

     if (cls == NULL) {

         goto destroy;

     }

 

     mid = (*env)->GetStaticMethodID(env, cls, "main",

                                     "([Ljava/lang/String;)V");

     if (mid == NULL) {

         goto destroy;

     }

     jstr = (*env)->NewStringUTF(env, " from C!");

     if (jstr == NULL) {

         goto destroy;

     }

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

     args = (*env)->NewObjectArray(env, 1, stringClass, jstr);

     if (args == NULL) {

         goto destroy;

     }

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

 

 destroy:

     if ((*env)->ExceptionOccurred(env)) {

         (*env)->ExceptionDescribe(env);

     }

     (*jvm)->DestroyJavaVM(jvm);

 }

上面的代码有条件地编译一个初始化JDK1_1InitArgs这个structure。这个structure是JDK1.1下特有的,尽管JDK1.2也会支持,但JDK1.2引入了一个更通用的叫作JavaVMInitArgs的VM初始化structure。

常量JNI_VERSION_1_2在JDK1.2下定义,JDK1.1下是没有的。

当目标平台是1.1时,C代码首先调用JNI_GetDefaultJavaVMInitArgs来获得默认的VM设置。这个调用会返回heap size、stack size、默认类路径等信息,并把这些信息存放在参数vm_args中。然后我们把Prog.class所在的目录附加到vm_args.classpath中。

当平台目标是1.2时,C代码创建了一个JavaVMInitArgs的structure。VM的初始化参数被存放在一个JavaVMOption数组中。

设置完VM初始化structure后,C程序调用JNI_CreateJavaVM来加载和初始化JVM,传入的前两个参数:

1、 接口指针jvm,指向新创建的JVM。

2、 当前线程的JNIEnv接口指针env。本地代码通过env指针访问JNI函数。

当函数JNI_CreateJavaVM函数成功返回时,当前本地线程已经把自己的控制权交给JVM。这时,它会就像一个本地方法一样运行。以后就可以通过JNI函数来启动Prog.main方法。

接着,程序调用DestroyJavaVM函数来unloadJVM。不幸的是,在JDK1.1和JDK1.2中你不能unloadJVM,它会一直返回一个错误码。

运行上面的程序,产生如下输出:

Hello World from C!

7.2 把本地程序和JVM链接在一起

通过调用接口,你可把invoke.c这样的程序和一个JVM链接到一起。怎么样链接JVM取决于本地程序是要和一个特定的VM一起工作,还是要和多个具体实现方式未知的不同VM一起工作。

7.2.1 和一个己知的JVM链接到一起

这种情况下,你可以把你的本地程序和实现了JVM的本地库链接在一起。编译链接成功后,你就可以运行得到的可执行文件。运行时,你可能会得到一个错误信息,比如“无法找到共享库或者动态链接库”,在Windows下,错误信息可能会指出无法发现动态链接库javai.dll(JDK1.1)或者jvm.dll(JDK1.2),这时,你需要把DLL文件加载到你的PATH环境变量中去。

7.2.2 和未知的多个JVM链接到一起

这种情况下,你就不能把本地程序直接和一个特定的库链接在一起了。比如,JDK1.1的库是javai.dll,而JDK1.2的库是jvm.dll。

解决方案是根据本地程序的需要,用运行时动态链接来加载不同的VM库。例如,下面的win32代码,根据给定的VM库的路径找到JNI_CreateJavaVM函数的入口。

LoadLibrary和GetProcAddress是Win32平台上用来动态链接的API。虽然LoadLibrary可以实现了JVM的本地库的名字(如“jvm”)或者路径(如“C:\\jdk1.2\\jre\\bin\\classic\\jvm.dll”)。最好把本地库的绝对路径传递给JNU_FindCreateJavaVM,让LoadLibrary去搜索jvm.dll,这样程序就不怕环境变量被改变了。

7.3 附加本地线程

假设,你有一个用C写的服务器这样的多线程程序。当HTTP请求进来的时候,服务器创建许多本地线程来并行的处理HTTP请求。为了让多个线程可以同时操作JVM,我们可能需要把一个JVM植入这个服务器。

图7.1 把JVM嵌入WEB服务器

服务器上的本地方法的生命周期一般会比JVM要短,因此我们需要一个方法把本地线程附加到一个已经在运行的JVM上面,然后在这个本地方法中进行JNI调用,最后在不打扰其它连接到JVM上的线程的情况下把这个本地线程和JVM分离。

下面这个例子中,attach.c演示了怎么样使用调用接口(invocation interface)把本地线程附加到VM上去,这段程序使用的是Win32线程API。

/* Note: This program only works on Win32 */

 #include <windows.h>

 #include <jni.h>

 JavaVM *jvm; /* The virtual machine instance */

 

 #define PATH_SEPARATOR ';'

 #define USER_CLASSPATH "." /* where Prog.class is */

 

 void thread_fun(void *arg)

 {

     jint res;

     jclass cls;

     jmethodID mid;

     jstring jstr;

     jclass stringClass;

     jobjectArray args;

     JNIEnv *env;

     char buf[100];

     int threadNum = (int)arg;

     /* Pass NULL as the third argument */

 #ifdef JNI_VERSION_1_2

     res = (*jvm)->AttachCurrentThread(jvm, (void**)&env, NULL);

 #else

     res = (*jvm)->AttachCurrentThread(jvm, &env, NULL);

 #endif

     if (res < 0) {

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

        return;

     }

     cls = (*env)->FindClass(env, "Prog");

     if (cls == NULL) {

         goto detach;

     }

     mid = (*env)->GetStaticMethodID(env, cls, "main", 

                                     "([Ljava/lang/String;)V");

     if (mid == NULL) {

         goto detach;

     }

     sprintf(buf, " from Thread %d", threadNum);

     jstr = (*env)->NewStringUTF(env, buf);

     if (jstr == NULL) {

         goto detach;

     }

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

     args = (*env)->NewObjectArray(env, 1, stringClass, jstr);

     if (args == NULL) {

         goto detach;

     }

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

 

  detach:

     if ((*env)->ExceptionOccurred(env)) {

         (*env)->ExceptionDescribe(env);

     }

     (*jvm)->DetachCurrentThread(jvm);

 }

 

 main() {

     JNIEnv *env;

     int i;

     jint res;

 

 #ifdef JNI_VERSION_1_2

     JavaVMInitArgs vm_args;

     JavaVMOption options[1];

     options[0].optionString =

         "-Djava.class.path=" USER_CLASSPATH;

     vm_args.version = 0x00010002;

     vm_args.options = options;

     vm_args.nOptions = 1;

     vm_args.ignoreUnrecognized = TRUE;

     /* Create the Java VM */

     res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);

 #else

     JDK1_1InitArgs vm_args;

     char classpath[1024];

     vm_args.version = 0x00010001;

     JNI_GetDefaultJavaVMInitArgs(&vm_args);

     /* Append USER_CLASSPATH to the default system class path */

     sprintf(classpath, "%s%c%s",

             vm_args.classpath, PATH_SEPARATOR, USER_CLASSPATH);

     vm_args.classpath = classpath;

     /* Create the Java VM */

     res = JNI_CreateJavaVM(&jvm, &env, &vm_args);

 #endif /* JNI_VERSION_1_2 */

 

 if (res < 0) {

         fprintf(stderr, "Can't create Java VM\n");

         exit(1);

     }

     for (i = 0; i < 5; i++)

         /* We pass the thread number to every thread */

         _beginthread(thread_fun, 0, (void *)i);

     Sleep(1000); /* wait for threads to start */

     (*jvm)->DestroyJavaVM(jvm);

 }

上面这段attach.c代码是invoke.c的一个变形。与在主线程中调用Prog.main不同,本地代码开启了五个线程。开启线程完成以后,它就会等待1秒钟让线程可以运行完毕,然后调用DestroyJavaVM来销毁JVM。而每一个线程都会把自己附加到JVM上面,然后调用Prog.main方法,最后断开与JVM的连接。

JNI_AttachCurrentThread的第三个参数需要传入NULL。JDK1.2引入了JNI_ThreadAttachArgs这个structure。它允许你向你要附加的线程传递特定的信息,如线程组等。JNI_ThreadAttachArgs这个structure的详细描述在13.2节里面,作为JNI_AttachCurrentThread的规范的一部分被提到。

当程序运行函数DetachCurrentThread时,它释放属于当前线程的所有局部引用。

运行程序,输出如下:

Hello World from thread 1

 Hello World from thread 0

 Hello World from thread 4

 Hello World from thread 2

 Hello World from thread 3

上面这些输出根据不同的线程调试策略,可能会出现不同的顺序。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值