- 讲师:刘萍萍 / 谢楠
- 课时:160h
- 价格 4580 元
特色双名师解密新课程高频考点,送国家电网教材讲义,助力一次通关
配套通关班送国网在线题库一套
百度广告
并非所有的开发者都清楚,时下最流行的两个程序运行环境(Java虚拟机JVM和.NET通用语言运行时CLR)事实上就是一组共享的类库。不论是JVM还是CLR,都为程序代码的执行提供了各种所需的功能服务,这其中包括内存管理、线程管理、代码编译(或Java特有的即时编译JIT)等等。由于这些特性的存在,在一个操作系统中,如果程序同时运行在JVM和CLR两种环境之上,由于任何一个进程都可以加载与之对应的任何共享类库,这使得相应的操作将变得非常繁琐。
然而,当话题讨论到这些问题的时候,大多数开发者都会停下来,向一侧仰着头,非常认真的问道"可是……这样的互操作对我们来说究竟有什么用?"
近些年来,基于Java平台的程序开发,一直都有为数众多的API类库和新技术为其提供强大的支持。与此同时,.NET的通用语言运行时CLR,天生就具备Windows操作系统所提供的那些丰富的编程支持。在Windows操作系统环境下,常有许多Windows编程中易于实现的功能目前却很难使用Java语言编程实现,然而有的时候,使用Java语言实现特定功能较之Windows编程却更为简洁。这是在Java编程中,使用Java本地接口JNI技术实现互操作时的通常看法,同时这对于Java的开发者来说也应当是非常熟悉。可能会让开发者感觉有所陌生的,是那些尝试在Java虚拟机中实现.NET编程语言特性的想法,例如在最新的.NET 3.0中,包含工作流、WPF和InfoCard等广受关注的特性,或是在.NET过程中使用Java虚拟机提供的工具,比如说部署Java语言编写的那些包含复杂业务逻辑的Spring组件,或者实现通过ASP.NET访问JMS消息队列这样的功能。
加载动态链接库以及与底层代码托管环境进行交互,是解决互操作问题所面临的两个不同问题,然而,每一项操作都为之提供了标准的应用程序接口来完成这样的功能。举例来说,下面列出的非托管C++代码来自于Java本地接口JNI的官方文档,目的是利用标准过程(相关的代码句柄在JNIHosting子目录里以InProcInterop方案的一部分存在,构建它的最好方法是在命令行里用指向JDK 1.6目录位置的JAVA_HOME环境变量来操作。 )创建基于Java虚拟机的函数调用
"stdafx.h" _tmain( argc, _TCHAR* argv) JavaVM *jvm; /* 表示一个Java虚拟机 */ JavaVMInitArgs vm_args; /* JDK或JRE 6的虚拟机初始化参数 */ JavaVMOption options; n = 0; vm_args.version = JNI_VERSION_1_6; /* 加载或初始化Java虚拟机,返回Java本地调用接口 /* 使用Java本地接口调用 Main.test 方法 */ /* 完成工作 */ |
|||
"stdafx.h" _tmain( argc, _TCHAR* argv) ICLRRuntimeHost* pCLR = (ICLRRuntimeHost*)0; STARTUP_CONCURRENT_GC, CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (FAILED(hr)) hr = pCLR->Start(); DWORD retval = 0; hr = pCLR->Stop(); ()retval; |
如同Java本地接口JNI的示例一样,上面的示例假定应用程序HelloWorld.exe在执行时与.NET编译(这儿,因为我们期望正用到的(ExecuteInDefaultAppDomain)这个特殊的宿主API能有一个调用它里面Hello的类,这个类要有一个名为Main的方法,以将一个字符串当作声明处理并返回整数值。注意这和传统的C#或者VB.NET的入口通道有所不同。)都位于当前目录之下。由于.NET通用语言运行时CLR与操作系统具备更紧密的集成关系,所以CLR的动态链接库路径不需要手动设置在环境变量的PATH路径之中(关于CLR启动程序如何进行工作处理,详细内容请参考《CLI的共享源代码实现》一书)。
当程序开发者使用非托管的C++代码编写应用成为可能,即可以加载CLR和JVM这两种不同的运行时环境来完成处理过程,这使得大部分业务逻辑的程序编写陷入开发者不敢去涉及的境地。然而吸引人的是,这可以作为锻炼编程技巧与能力的一种方式,对于我们大多数人,在这个过程中都会找到一系列的替代方案。
首先,比如说,CLR和JVM两种技术都支持非托管代码的"Calling Down"操作(在Java虚拟机中,被称作Java本地接口,而在.NET的CLR中,被称作P/Invoke调用),这样的机制使得开发者可以在其中一个运行环境下定义功能方法,通过少量的"Trampoline(弹簧床)"编码,将程序迁移到另一个运行时环境下编译执行。例如,在Java程序中,通过本地方法接口JNI实现函数的调用操作较为繁琐,并且需要记录配置文档(比如,可以参见Liang或者Gordon的书,或者JDK中JNI的文档。)。而在实现C++本地代码调用的过程中,较为繁琐的操作是使用微软Visual Studio 2005中提供的C++/CLI或Visual Studio 2003提供的C++托管代码,来进行代码编译的过程。
在这个步骤中,复杂之处在于程序运行时,需要确保Java虚拟机得到访问动态链接库的路径。这项工作可以分为两部分来完成:首先,当Java类函数的本地方法被程序加载时,需要询问Java虚拟机是否通过Runtime.loadLibrary()操作来请求加载共享库函数。值得注意的是,本地类库请求是在没有指定文件拓展名的情况下完成这样的操作。不指定拓展名,是因为不同的操作系统往往使用不同的约定来共享类库,所以只需指定共享类库名称即可。比如在Windows操作系统下,共享类库具有.DLL后缀,然而在Unix或Linux操作系统之下,共享类库常用的约定是使用类似于libNAME.so这样的名称。就这方面来讲,Java虚拟机首先需要在特定的操作系统中查询共享类库的约定惯例。在Windows操作系统之下,针对于加载类库的LoadLibrary()函数,官方文档中有明确的API接口说明,但所需的类库通常都包含在操作系统的安装目录中(在Windows操作系统中即为C:"WINDOWS 和 C:"WINDOWS"SYSTEM32目录),或是当前的工作目录,或者已经包含在环境变量PATH的设定之中。对于Java虚拟机的类库调用,也需要在其他两个目录中查找,即在由java.library.path系统参数指定的目录中,或是JRE运行环境所在目录的lib"i386路径之下。通常来说,推荐使用的方法是在自定义属性java.library.path中指定本地代码执行参数(在Java虚拟机启动的时候,可以设置好系统参数的路径),或者指定在JRE运行环境的i386目录中。在这个特定的例子中,很容易想象的到,指定Java虚拟机的系统参数常常是出乎开发者预期的事情(因为有时可能会有数目众多的应用服务需要设置),所以有时动态链接函数库需要被Servlet容器或应用服务器复制到Java运行环境下的函数库Lib之中。当DLL动态链接库被应用程序发现时,事实上这种所谓"混合模式"的.NET动态链接方式(即同时管理托管和非托管的代码),将会强制CLR通用语言运行时在进程启动时自动绑定,并且使得.NET通用语言运行时提供的全部功能,都集中体现在Java本地接口的动态链接库提供的操作之中。
值得一提的是,.NET应用可以通过Trampoline(弹簧床)机制,调用Java程序代码,并使用非托管的动态链接库。然而,Java虚拟机不包含.NET所具有的那些Bootstrapping引导等神奇的机制(即"一次编写,到处运行"的特性),在进程调用中,非托管的动态链接库需要正确的加载Java虚拟机,通过与先前一样的方式来使用相同的API程序调用接口。一旦Bootstrapping引导机制就位,使用Java本地接口的反射机制,就像API调用允许类库加载,对象创建和方法调用的过程一样。通过.NET CLR程序代码来访问非托管的动态链接库,实现起来仅是如何去调用P/Invoke接口的过程,并且接口调用过程具备详尽的文档说明。 |||
如果所有这些工作,看起来需要占用很多的时间来完成,那一定会有人帮你想到更简洁的解决方法。幸运的是,已有相关的工具和技术让这个过程变得非常简单。
首先来看一款开源的工具包JACE(http://jace.sourceforge.net),JACE可以简化JNI本地调用的互操作过程,其设计目的是使得编写符合JNI规范的代码变得轻松简单,特别是对于Java虚拟机的Bootstrapping引导机制方面。JACE的功能相对完善,并且JACE为非托管的C++代码提供支持,这样可能意味着我们仍然需要反过头来以Windows动态链接库的方式编写各种"不安全"的代码。
另外还有一个叫做IKVM的开源类库,现在已经成为Mono项目的一个部分。IKVM在JVM(现在(和可预见的未来)IKVM只会从CLR到JVM,不会反过来。)和CLR之间搭建了桥梁,为Java与.NET互操作提供了与其他已提到解决方案不同的实现途径。IKVM的实现并非是将Java字节码翻译成CIL代码,所以不需要将JVM加载到同一个进程之中。这包含一些有趣的含义,既然Java虚拟机没有被加载,在代码中就不需要考虑Java虚拟机所需的运行机制:即不需要Hotspot技术,不具备JMX监测程序(这意味着没有Java控制台来监测你的Java代码运行)等等。当然,既然所有的代码将转化为CIL语言,就可以利用.NET CLR通用语言运行时的所有益处,这些功能包括:CLR通用语言运行时的JIT即时编译技术,CLR性能监视器统计等功能。自从IKVM可以执行字节码翻译之后,这样的效果就对于CLR的开发者来说就变得相对透明。
然而,我们也可能真的需要加载Java虚拟机环境,并且代码的过程代理需要在程序中释放,就像Codemesh的JuggerNET工具(JuggerNET是Java-C++代理工具的.NET版本。)生成的代码那样。它提供了两个功能:可以与.NET完善集成的Java本地接口调用API,使其可以更方便的使用.NET环境创建Java应用程序,并且提供.NET代码生成器产生.NET的代理程序,用来配置必须的参数并且执行Java对象中定义的函数方法。这样,使用JuggerNET在.NET应用中加载JVM程序的示例代码应该符合下面的过程:
/* */ Codemesh.JuggerNET; // { //-------------------------------------------------------------------- // 设置classpath参数为当前的工作目录 // 在classpath中添加CWD的父目录 // 设置堆栈的最大值 // 设置一组 -D 选项 // 指定 TraceFile记录文件.如果不指定,所有的记录输出将会加入到 stderr标准错误之中 //-------------------------------------------------------------------- } Console.WriteLine( "*************** we're leaving Main() ****************" ); |
|||
/* */ System; /// /// 通过拓展序列化的代理接口,我们自动为.NET类型产生被称为"peer"的参数。 /// 并且使用Java同等的类型来保持.NET实例的序列化信息。 MyDotNetClass : Java.Io.Serializable field1 = 0; strField = ""; MyDotNetClass() MyDotNetClass( f1, f2, s ) ToString() /// /// 但是声明为不同类型的数据元素。 MyDotNetClass2 : Java.Io.Serializable test = { 0, 1, 2 }; MyDotNetClass2() MyDotNetClass2( f1, f2 ) ToString() result.Append( "MyDotNetClass2[test=[" ); } result.ToString(); } /// /// 通过为.NET类型添加JavaPeer属性。 /// 但是有些不很方便的地方是,在需要使用Serializable的时候, /// JavaPeer属性列出了两个不同的属性: /// 第一个属性指定保持数据的Java类型, /// PeerMarshaller= "Codemesh.JuggerNET.ReflectionPeerValueMarshaller")] { /// /// ToString() /// /// /// /// /// /// 但是对于Java,这个字段是归类在'CustomFieldName'之下。 /// 并且需要访问自己的数据,则可以对其加以关注。 [JavaPeer(Name="CustomFieldName")] ToString() "PureDotNetType2[NotUsed=" + NotUsed + ", OnlyUsedField=" + OnlyUsedField + "]"; } Peer ( args.Length > 1 && args[ 0 ].Equals( "-info") ) // 生成哈希表的实例 // 创建一些纯.NET实例 obj3.CharProperty = 'B'; // 这两个值将在我们的哈希表中得到对象返回值后被消除 // 将.NET 实例放入Java哈希表, // 这是一个真实的测试! o2 = ht.Get( "obj2" ); o3 = ht.Get( "obj3" ); o4 = ht.Get( "obj4" ); Console.WriteLine( "ht={0}", ht.ToString() ); |
|||
集中化。在许多情况下,我们希望特定资源(比方说代码中的数据库序列标识符)只存在于一个且仅此一个进程之中,来避免复杂的进程间代码同步的实现。
可靠性。较少的硬件相关性,以及整个系统单一的硬件损耗,使得系统很少会有受到攻击的可能性。
结构化要求。在某些情况下,现有的结构化模型将要求所有程序处理过程替代已有的处理过程,比如说,应用程序的现有用户接口如果使用ASP.NET编写,并且应用程序部分的互操作性,用以实现为EJB消息驱动Bean在JMS消息队列中的消息传送处理过程。则在本地程序中传送消息给Java服务,并且仅是释放消息到JMS队列之中,这样的过程就显得有些多余,特别是在假定JMS客户端代码非常简洁的时候,程序实现代价较高。将JMS的客户端代码放入ASP.NET进程之中(Codemesh为JuggerNET代理实现JMS消息客户端提供了特别的版本),来实现与现有程序架构保持一致的简洁途径。
此外,并非是所有的互操作解决方案都将通过in-proc方法来实现,但其中一些会使用这样的方法,并且开发者无需害怕这样的想法,即便是提供这些操作的工具有着非常大的使用价值。
关于Ted Neward是大规模企业应用系统方面的独立咨询人。也是Java、.NET和XML服务相关主题的会议上的演讲人,致力于Java与.NET的互操作技术。在Java与.NET方面,他曾撰写过几本广受认可的书籍,其中包括最近出版的《高效企业级Java开发》一书。
资源"The Java Native Interface" (Liang)
"Java Native Interface" (Gordon)
The JNI page at the Java SE website (http://java.sun.com/javase/6/docs/technotes/guides/jni/index.html)
"Customizing the Common Language Runtime" (Pratschner)
"Shared Source CLI" (Stutz, Neward, Shilling)
The C++/CLI Language Specification (ECMA International)
责编:罗莉
课程专业名称 |
讲师 |
课时 |
查看课程 |
---|
课程专业名称 |
讲师 |
课时 |
查看课程 |
---|
点击加载更多评论>>