封装 JNI 控制 JVM [1] 配置并构造 JVM

时间:2021-10-03 19:31:39
http://www.blogcn.com/user8/flier_lu/index.html?id=1934417&run=.09DD601

前两天有位朋友问到在 .NET 里面调用 Java 类方法并返回值的方法,刚好手头工作可能会需要用 JNI 来封装现有系统,于是晚上写了个调用例子,顺手对 JNI 的基本调用做了一个简单的封装。因为 JNI 的接口设计上主要面向 C++ 语言,因此决定将 JNI 的封装放在一个独立的 DLL 中进行,然后在 .NET 中通过 Interop 再做一层封装,反正这种需求下效率也不是首要考虑因素。
     JNI 的原理和基本使用方法这里就不多说了,现成的资料太多了,有兴趣的朋友可以参考 sun 的 JNI  文档以及 示例。此外 Sheng Liang 的  Java Native Interface: Programmer's Guide and Specification 一书中对 JNI 做了较为全面的解析。
     JNI 的封装其实现成的有很多,如 sourceforge.net 上的  Jace 等; Java 和 .NET 互操作的封装也有一些,如  JVM-CLR Bridge 等。但一方面自己的需求与他们不太一样,另一方面也希望通过自行开发增加对这方面知识的了解,故而选择自己重新发明*,呵呵,权且当作是一种娱乐吧 :P

 [1] 配置并构造 JVM

     JVM 的配置与构造与 CLR 的非常类似(CLR 的相关使用方法可以参考《.Net平台下CLR程序载入原理分析 [草稿]》一文 )。用户程序通过 JRE 的 jvm.dll 提供的接口,构造并配置虚拟机,调用相应的类的方法,完成实际工作。从 JDK 1.2 开始,JRE 提供了 client/server 两个版本的 jvm.dll,一般可以在 %JRE%inclient 和 %JRE%inserver 目录下找到,和 CLR 中的 wks/srv 类似。jvm.dll 提供了 JNI_CreateJavaVM 函数用于构造虚拟机,对应于 CLR 中 mscoree.dll 提供的 CorBindToRuntimeEx 函数。此函数的定义可以在 %JDK%includejni.h 头文件里面找到,静态链接可以通过 %JDK%libjvm.lib 库文件完成,动态连接则可以根据选用 JRE 的虚拟机类型从相应 jvm.dll 中使用 GetProcAddress 函数获得入口。
 

以下为引用:

 _JNI_IMPORT_OR_EXPORT_ jint JNICALL JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *args);
 


     pvm 参数返回虚拟机的对象指针,penv 参数返回当前线程执行环境的对象指针,args 参数传入虚拟机构造时使用的配置参数。
     一般来说,每个进程同时只能启动一个 JVM 实例,这在很多 JVM 实现中是硬编码在虚拟机构造代码中。因此使用者应该通过 singleton 模式保障全局 JVM 实例的唯一性,或者通过 JNI_GetCreatedJavaVMs 函数获取已经构造的虚拟机对象指针。而 JavaVM::DestroyJavaVM 方法,大多数情况下是不起作用的,只是需要在程序结束是礼节性的调用一下,不要指望通过它真正卸载 JVM,否则再次调用 JNI_CreateJavaVM 函数时可能会发生错误。一个简单的封装如下:
 
以下为引用:

 namespace JBridge
 {
  public class JStub
  {
     [DllImport("JvmStub.dll")]
     public static extern IntPtr CreateJVM();

     [DllImport("JvmStub.dll")]
     public static extern void DestroyJVM(IntPtr vm);
   }
   
   public class JVM 
   {
     static private IntPtr _vm = IntPtr.Zero;    

     private JVM()
     {
     }

     ~JVM()
     {
       if(_vm != IntPtr.Zero)
       {
         JStub.DestroyJVM(_vm);
         _vm = IntPtr.Zero;
       }      
     }

     public static IntPtr getInstance()
     {
       if(_vm == IntPtr.Zero)
       {
         lock(typeof(JVM))
         {
           if(_vm == IntPtr.Zero)
           {
             _vm = JStub.CreateJVM();
           }
         }
       }
       return _vm;
     }
   }
 }
 


    
     而在一个创建了 JVM 的进程中,并不是所有线程都能够直接使用 JVM 的,需要通过 JavaVM::AttachCurrentThread 方法将当前线程挂接到 JVM 环境上,并获取当前线程执行环境的对象指针 JNIEnv *pEnv;在无需处理 JVM 调用时,应该调用 JavaVM::DetachCurrentThread 方法析构 JVM 为当前线程准备的数据;而 JNI_CreateJavaVM 函数在构造 JVM 时缺省返回调用线程的执行环境对象指针。
 
以下为引用:

 struct JavaVM_
 {
   jint DestroyJavaVM()
   {
     // ...
   }
   jint AttachCurrentThread(void **penv, void *args)
   {
     // ...
   }
   jint DetachCurrentThread()
   {
     // ...
   }
 }
 


     最后的 args 参数是配置虚拟机以何种方式启动。
 
以下为引用:

 #define JNI_VERSION_1_1 0x00010001
 #define JNI_VERSION_1_2 0x00010002
 #define JNI_VERSION_1_4 0x00010004

 typedef struct JavaVMInitArgs {
   jint version;
   jint nOptions;
   JavaVMOption *options;
   jboolean ignoreUnrecognized;
 } JavaVMInitArgs;
 



     字段 version 指定虚拟机使用的兼容版本,如 jni.h 定义的 JNI_VERSION_1_2;
     字段 nOptions 和 options 指定虚拟机使用哪些配置。
 
以下为引用:

 typedef struct JavaVMOption {
   char *optionString;
   void *extraInfo;
 } JavaVMOption;
 


     配置信息以字符串形式保存在 JavaVMOption::optionString 中,由 JVM 进行具体解析。常用的配置有诸如显示 JNI 调试信息(-verbose:jni);对 JNI 调用进行额外检查(-Xcheck:jni);以及指定 Java 类(-Djava.class.path=???)、JNI 本地库(-Djava.library.path=???)、初始化类库(-Xbootclasspath:???)等等路径。
     一个实际的配置和构造 JVM 示例代码如下:
 
以下为引用:

 JavaVMOption opts[5];

 opts[0].optionString = "-Djava.class.path=F:\Study\Java\JNI\Prompt";
 opts[1].optionString = "-Djava.library.path=F:\Study\Java\JNI\Prompt";
 opts[2].optionString = "-verbose:jni";
 opts[3].optionString = "-Xcheck:jni";
 opts[4].optionString = "-Xbootclasspath:E:\Borland\JBuilderX\jdk1.4\jre\lib\rt.jar";

 JavaVMInitArgs args;

 args.version = JNI_VERSION_1_2;
 args.options = opts;
 args.nOptions = sizeof(opts) / sizeof(opts[0]);
 args.ignoreUnrecognized = JNI_TRUE;

 Check(JNI_CreateJavaVM(&m_jvm, reinterpret_cast<void **>(static_cast<JNIEnv **>(m_env)), &args));