写点什么

互操作现在进行时

  • 2007-06-01
  • 本文字数:13015 字

    阅读完需:约 43 分钟

并非所有的开发者都清楚,时下最流行的两个程序运行环境(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 的官方文档,目的是利用标准过程 1 创建基于 Java 虚拟机的函数调用:

<span color="#0000ff">#include</span> <span color="#660033">"stdafx.h"</span><br></br><span color="#0000ff">#include</span> <span color="#660033"><jni.h></jni.h></span><p><span color="#0000ff">int</span> _tmain(<span color="#0000ff">int</span> argc, _TCHAR* argv[])</p><br></br>{<br></br> JavaVM *jvm; <span color="#006600">/* 表示一个 Java 虚拟机 */</span><br></br> JNIEnv *env; <span color="#006600">/* 指向本地方法调用接口 */</span><br></br> JavaVMInitArgs vm_args; <span color="#006600">/* JDK 或 JRE 6 的虚拟机初始化参数 */</span><p> JavaVMOption options[4]; <span color="#0000ff">int</span> n = 0;</p><br></br> options[n++].optionString = <span color="#660033">"-Djava.class.path=.";</span><p> vm_args.version = JNI_VERSION_1_6;</p><br></br> vm_args.nOptions = n;<br></br> vm_args.options = options;<br></br> vm_args.ignoreUnrecognized = <span color="#0000ff">false;</span><p><span color="#006600">/* 加载或初始化 Java 虚拟机,返回 Java 本地调用接口 <br></br> * 指向变量 env */</span> JNI_CreateJavaVM(&jvm, (<span color="#0000ff">void</span>**)&env, &vm_args); <span color="#006600">// 传入 C++ 所需的参数 </span></p><p><span color="#006600">/* 使用 Java 本地接口调用 Main.test 方法 */</span> jclass cls = env->FindClass(<span color="#660033">"Main"</span>);</p><br></br> jmethodID mid = env->GetStaticMethodID(cls, <span color="#660033">"test", "(I)V"</span>);<br></br> env->CallStaticVoidMethod(cls, mid, 100);<p><span color="#006600">/* 完成工作 */</span> jvm->DestroyJavaVM();</p><p><span color="#0000ff">return 0;</span> }</p>在编译上述代码时,Java 开发工具包 JDK 中的 include 和 include\win32 目录将被添加在 C++ 程序的 include 路径中,并且 JDK 中 lib 目录下的 jvm.lib 必须位于目标代码连接器的路径之中。程序运行时,默认情况下程序的主类 Main.class 作为程序执行的入口类,与上述文件位于相同的目录之中,并且保证 Java 运行环境 JRE 中的 jvm.dll 动态链接库存在,一般来说这个动态链接库是存在于系统环境变量的 PATH 路径之中。(jvm.dll 通常不需要手动添加在 PATH 路径中,因为 java.exe 将会动态的查找 jvm.dll 动态链接库的位置,并在找到链接库后记录下它的位置。)

同样,.NET 通用语言运行时 CLR 提供自有的应用程序调用接口,作为本地 API 接口来实现同样的功能,代码如下:

<span color="#0000ff">#include</span> <span color="#660033">"stdafx.h"</span><br></br><span color="#0000ff">#include</span> <span color="#660033"><mscoree.h></mscoree.h></span><p><span color="#0000ff">int</span> _tmain(<span color="#0000ff">int</span> argc, _TCHAR* argv[])</p><br></br> {<br></br> ICLRRuntimeHost* pCLR = (ICLRRuntimeHost*)0;<br></br> HRESULT hr = CorBindToRuntimeEx(NULL, L<span color="#660033">"wks"</span>,<br></br> STARTUP_CONCURRENT_GC, CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost,<br></br> (PVOID*)&pCLR);<br></br><span color="#0000ff">if</span> (FAILED(hr))<br></br><span color="#0000ff">return</span> -1;<p> hr = pCLR->Start();</p><br></br><span color="#0000ff">if</span> (FAILED(hr))<br></br><span color="#0000ff">return</span> -1;<p> DWORD retval = 0;</p><br></br> hr = pCLR->ExecuteInDefaultAppDomain(L<span color="#660033">"HelloWorld.exe"</span>, L<span color="#660033">"Hello"</span>, L<span color="#660033">"Main"</span>, NULL, &retval);<br></br><span color="#0000ff">if</span> (FAILED(hr))<br></br><span color="#0000ff">return</span> -1;<p> hr = pCLR->Stop();</p><br></br><span color="#0000ff">if</span> (FAILED(hr))<br></br><span color="#0000ff">return</span> -1;<p><span color="#0000ff">return</span> (<span color="#0000ff">int</span>)retval;</p><br></br> }如同 Java 本地接口 JNI 的示例一样,上面的示例假定应用程序 HelloWorld.exe 在执行时与.NET 编译 2 都位于当前目录之下。由于.NET 通用语言运行时 CLR 与操作系统具备更紧密的集成关系,所以 CLR 的动态链接库路径不需要手动设置在环境变量的 PATH 路径之中(关于 CLR 启动程序如何进行工作处理,详细内容请参考《CLI 的共享源代码实现》一书)。

当程序开发者使用非托管的 C++ 代码编写应用成为可能,即可以加载 CLR 和 JVM 这两种不同的运行时环境来完成处理过程,这使得大部分业务逻辑的程序编写陷入开发者不敢去涉及的境地。然而吸引人的是,这可以作为锻炼编程技巧与能力的一种方式,对于我们大多数人,在这个过程中都会找到一系列的替代方案。

首先,比如说,CLR 和 JVM 两种技术都支持非托管代码的“Calling Down”操作 (在 Java 虚拟机中,被称作 Java 本地接口,而在.NET 的 CLR 中,被称作 P/Invoke 调用), 这样的机制使得开发者可以在其中一个运行环境下定义功能方法,通过少量的“Trampoline(弹簧床)”编码,将程序迁移到另一个运行时环境下编译执行。例如,在 Java 程序中,通过本地方法接口 JNI 实现函数的调用操作较为繁琐,并且需要记录配置文档 3 。而在实现 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 4 和 CLR 之间搭建了桥梁,为 Java 与.NET 互操作提供了与其他已提到解决方案不同的实现途径。IKVM 的实现并非是将 Java 字节码翻译成 CIL 代码,所以不需要将 JVM 加载到同一个进程之中。这包含一些有趣的含义,既然 Java 虚拟机没有被加载,在代码中就不需要考虑 Java 虚拟机所需的运行机制:即不需要 Hotspot 技术,不具备 JMX 监测程序(这意味着没有 Java 控制台来监测你的 Java 代码运行)等等。当然,既然所有的代码将转化为 CIL 语言,就可以利用.NET CLR 通用语言运行时的所有益处,这些功能包括:CLR 通用语言运行时的 JIT 即时编译技术,CLR 性能监视器统计等功能。自从 IKVM 可以执行字节码翻译之后,这样的效果就对于 CLR 的开发者来说就变得相对透明。

然而,我们也可能真的需要加载 Java 虚拟机环境,并且代码的过程代理需要在程序中释放,就像 Codemesh 的 JuggerNET 工具 5 生成的代码那样。它提供了两个功能:可以与.NET 完善集成的 Java 本地接口调用 API,使其可以更方便的使用.NET 环境创建 Java 应用程序,并且提供.NET 代码生成器产生.NET 的代理程序,用来配置必须的参数并且执行 Java 对象中定义的函数方法。这样,使用 JuggerNET 在.NET 应用中加载 JVM 程序的示例代码应该符合下面的过程:

<span color="#006600"> /*<br></br> * Copyright 1999-2006 by Codemesh, Inc. ALL RIGHTS RESERVED.<br></br> */</span><br></br><span color="#0000ff">using</span> System;<br></br><span color="#0000ff">using</span> Codemesh.JuggerNET;<p><span color="#006600"> //<br></br> // 下面的代码设定 JVM 环境并且在程序中进行 Java 调用。<br></br> //<br></br> // 使用的 Java 虚拟机由平台依赖的业务逻辑决定。<br></br> // 在这个例子中,也可以使用 JvmPath 属性来设置程序将要使用的 JVM。<br></br></span><span color="#0000ff">public class</span> Application</p><br></br> {<br></br><span color="#0000ff">public static void</span> Main( string[] argv )<br></br> {<p><span color="#0000ff">try</span> {</p><p><span color="#006600">//--------------------------------------------------------------------<br></br> // 下面的代码提供了访问一个对象的途径,你可以使用这个对象来初始化运行时设置。<br></br> //</span> IJvmLoader loader = JvmLoader.GetJvmLoader();</p><p><span color="#006600"> //--------------------------------------------------------------------<br></br> // 配置 Java 设置 <br></br> //</span><span color="#006600"> // 设置 classpath 参数为当前的工作目录 </span> loader.ClassPath = ".";</p><p><span color="#006600"> // 在 classpath 中添加 CWD 的父目录 </span> loader.AppendToClassPath( ".." );</p><p><span color="#006600"> // 设置堆栈的最大值 </span> loader.MaximumHeapSizeInMB = 256;</p><p><span color="#006600"> // 设置一组 -D 选项 </span> loader.DashDOption[ <span color="#660033">"myprop"</span> ] = <span color="#660033">"myvalue"</span>;</p><br></br> loader.DashDOption[ <span color="#660033">"prop_without_value"</span> ] = <span color="#0000ff">null</span>;<p><span color="#006600"> // 指定 TraceFile 记录文件. 如果不指定,所有的记录输出将会加入到 stderr 标准错误之中 </span> loader.TraceFile = <span color="#660033">".\\trace.log"</span>;</p><p><span color="#006600"> //--------------------------------------------------------------------<br></br> // 你可以将这一项置空,在第一个代理操作执行时,或是可以精确加载 Java 虚拟机的时候,</span><span color="#006600"><br></br> // 使用配置设置来去除程序对于 JVM 环境的需求。<br></br> // 如果有错误发生,将会抛出一个异常。<br></br> //</span> loader.Load();</p><p> }</p><br></br><span color="#0000ff">catch</span>( System.<span color="#006699">Exception</span> )<br></br> {<br></br><span color="#006699">Console</span>.WriteLine( <span color="#660033">"!!!!!!!!!!!!!!! we caught an exception !!!!!!!!!!!!!!!!"</span> );<br></br> }<p><span color="#006699">Console</span>.WriteLine(<span color="#660033"> "*************** we're leaving Main() ****************" </span>);</p><p><span color="#0000ff">return;</span> }</p><br></br> }.NET 到 Java 代码生成的代理机制中,具备一定的编程技巧,因为存在一些手动设置来指定哪一个 Java 类和包应该被设为代理,实现这样的过程可以使用 JuggerNET 的 GUI 工具来指定描述包和类清单的模型文件,或者可以使用 Ant 脚本(这意味着一部分或全部的.NET 程序发布需要使用 Java 的 Ant 工具来实现,对于互操作项目来说,这并非是完全不切合实际的),通过使用"

<span color="#006600"> /*<br></br> * Copyright 1999-2006 by Codemesh, Inc. ALL RIGHTS RESERVED.<br></br> */</span><p><span color="#0000ff">using</span> System;</p><br></br><span color="#0000ff">using</span> Codemesh.JuggerNET;<br></br><span color="#0000ff">using</span> Java.Lang;<br></br><span color="#0000ff">using</span> Java.Util;<p><span color="#808080">/// <summary></summary><br></br> ///</span><span color="#006600"> 使用.NET 类型来定义数据成员。</span><span color="#808080">///</span><span color="#006600"> 通过拓展序列化的代理接口,我们自动为.NET 类型产生被称为"peer"的参数。</span><span color="#808080">///</span><span color="#006600"> 序列化接口在代码生成器中进行标记,</span><span color="#808080">///</span><span color="#006600"> 并且使用 Java 同等的类型来保持.NET 实例的序列化信息。</span><span color="#808080">/// </span><span color="#0000ff">public class</span> MyDotNetClass : Java.Io.Serializable</p><br></br> {<br></br><span color="#0000ff">public int</span> field1 = 0;<br></br><span color="#0000ff">public int</span> field2 = 1;<br></br><span color="#0000ff">public string</span> strField = <span color="#660033">"<not set=""></not>"</span>;<p><span color="#0000ff">public</span> MyDotNetClass()</p><br></br> {<br></br> }<p><span color="#0000ff">public</span> MyDotNetClass( <span color="#0000ff">int</span> f1, <span color="#0000ff">int</span> f2, <span color="#0000ff">string</span> s )</p><br></br> {<br></br> field1 = f1;<br></br> field2 = f2;<br></br> strField = s;<br></br> }<p><span color="#0000ff">public override string</span> ToString()</p><br></br> {<br></br><span color="#0000ff">return</span> <span color="#660033">"MyDotNetClass[field1="</span> + field1 + <span color="#660033">", field2="</span> + field2 + <span color="#660033">", strField='"</span> + strField + <span color="#660033">"']";</span><br></br> }<br></br> }<p><span color="#808080">///<summary></summary><br></br> ///</span><span color="#006600"> 另一个.NET 的类型继承自 Serializable,</span><span color="#808080">///</span> <span color="#006600"> 但是声明为不同类型的数据元素。</span></p><p><span color="#808080">/// </span><span color="#0000ff">public class</span> MyDotNetClass2 : Java.Io.Serializable</p><br></br> {<br></br><span color="#0000ff">public int</span>[] test = <span color="#0000ff">new int</span>[] { 0, 1, 2 };<p><span color="#0000ff">public</span> MyDotNetClass2()</p><br></br> {<br></br> }<p><span color="#0000ff">public</span> MyDotNetClass2( <span color="#0000ff">int</span> f1, <span color="#0000ff">int</span> f2 )</p><br></br> {<br></br> test[ 0 ] = f1;<br></br> test[ 1 ] = f2;<br></br> }<p><span color="#0000ff">public override string</span> ToString()</p><br></br> {<br></br> System.Text.<span color="#006699">StringBuilder</span> result = <span color="#0000ff">new</span> System.Text.<span color="#006699">StringBuilder</span>();<p> result.Append( <span color="#660033">"MyDotNetClass2[test=["</span> );</p><br></br><span color="#0000ff">for</span> (<span color="#0000ff">int</span> i = 0; i < test.Length; i++)<br></br> {<br></br><span color="#0000ff">if</span>( i != 0 )<br></br> result.Append(<span color="#660033"> ","</span> );<br></br> result.Append( <span color="#660033">""</span> + test[i] );<p> }</p><br></br> result.Append(<span color="#660033"> "]]"</span> );<p><span color="#0000ff">return</span> result.ToString(); }</p><br></br> }<p><span color="#808080">/// <summary></summary><br></br> ///</span><span color="#006600"> 这个类型阐明了如何实现等同序列化的目标。 </span><span color="#808080">///</span><span color="#006600"> 通过为.NET 类型添加 JavaPeer 属性。</span><span color="#808080">///</span><span color="#006600"> 创建相似的用法来继承 Java.Io.Serializable</span><span color="#808080">///</span><span color="#006600"> 但是有些不很方便的地方是,在需要使用 <t></t>Serializable 的时候,</span><span color="#808080">///</span><span color="#006600"> 在 <c></c>PureDotNetType 处不能使用生成的实例。</span><span color="#808080">///</span><span color="#006600"> <c></c>JavaPeer 属性列出了两个不同的属性:</span><span color="#808080">///</span><span color="#006600"> <c></c> 分别是 <c></c>PeerType 和 <c></c>PeerMarshaller。</span><span color="#808080">///</span><span color="#006600"> 第一个属性指定保持数据的 Java 类型,</span><span color="#808080">///</span><span color="#006600"> 第二个属性指定如何序列化.NET 实例来生成 Java 实例及其逆过程。</span><span color="#808080">/// </span> [JavaPeer(PeerType=<span color="#660033"> "com.codemesh.peer.SerializablePeer"</span>,</p><br></br> PeerMarshaller=<span color="#660033"> "Codemesh.JuggerNET.ReflectionPeerValueMarshaller"</span>)]<br></br><span color="#0000ff">public class</span> PureDotNetType<br></br> {<br></br><span color="#0000ff">private char</span> ch = <span color="#660033">'a'</span>;<p><span color="#808080">/// <br></br> ///</span><span color="#006600"> 一个字段的设置来帮助我们阐明从 Java 中读出的实际信息。</span><span color="#808080">/// </span><span color="#0000ff">public char</span> CharProperty</p><br></br> {<br></br><span color="#0000ff">set</span> { ch = value; }<br></br> }<p><span color="#0000ff">public override string</span> ToString()</p><br></br> {<br></br><span color="#0000ff">return</span> <span color="#660033">"PureDotNetType[ch='"</span> + ch + <span color="#660033">"']"</span>;<br></br> }<br></br> }<p><span color="#808080">/// </span><span color="#808080">///</span><span color="#006600"> 类型阐明了控制同等序列化细节的字段属性。</span><span color="#808080">/// </span> [JavaPeer(PeerType=<span color="#660033">"com.codemesh.peer.SerializablePeer"</span>,</p><br></br> PeerMarshaller=<span color="#660033">"Codemesh.JuggerNET.ReflectionPeerValueMarshaller"</span>)]<br></br><span color="#0000ff">public class</span> PureDotNetType2<br></br> {<p><span color="#808080">///<summary></summary><br></br> ///</span><span color="#006600"> 在去除编组之后的字段值将一直保持是'42',因为它的值没有被序列化或反序列化。</span><span color="#808080">/// </span> [<span color="#006699">NonSerialized</span>]</p><br></br><span color="#0000ff">public int</span> NotUsed = 42;<p><span color="#808080"> /// <summary></summary><br></br> ///</span><span color="#006600"> 在去除编组之后的字段值将一直保持是空值,因为它的值没有被序列化或反序列化。</span><span color="#808080">/// </span> [JavaPeer(Ignore=<span color="#0000ff">true</span>)] <span color="#0000ff">public string</span> AlsoNotUsed = <span color="#0000ff">null</span>;</p><p><span color="#808080">///<summary></summary><br></br> ///</span><span color="#006600"> 这个字段的值经过序列化或反序列化,</span><span color="#808080">///</span><span color="#006600"> 但是对于 Java,这个字段是归类在'CustomFieldName'之下。</span><span color="#808080">///</span><span color="#006600"> 你可能通常不会关心 Java 的名称,但是如果 Java 程序可以访问 peer 对象,</span><span color="#808080">///</span><span color="#006600"> 并且需要访问自己的数据,则可以对其加以关注。</span><span color="#808080">/// </span> [JavaPeer(Name=<span color="#660033">"CustomFieldName"</span>)]</p><br></br><span color="#0000ff">public int</span> OnlyUsedField = 2;<p><span color="#0000ff">public override string</span> ToString()</p><br></br> {<br></br><span color="#0000ff">return</span> <span color="#660033">"PureDotNetType2[NotUsed="</span> + NotUsed +<br></br><span color="#660033">", AlsoNotUsed="</span> + ( AlsoNotUsed == null ? <span color="#660033">"null"</span> : AlsoNotUsed ) +<br></br><span color="#660033">", OnlyUsedField="</span> + OnlyUsedField + <span color="#660033">"]"</span>;<br></br> }<br></br> }<p><span color="#0000ff">public class</span> Peer</p><br></br> { <span color="#0000ff">public static void</span> Main( string[] args )<br></br> {<p><span color="#0000ff">try</span> {</p><br></br> IJvmLoader loader = JvmLoader.GetJvmLoader();<p><span color="#0000ff">if</span>( args.Length > 1 && args[ 0 ].Equals( <span color="#660033">"-info"</span>) )</p><br></br> ;<span color="#006600">//loader.PrintLdLibraryPathAndExit();<p> // 生成哈希表的实例 </p></span><br></br> Java.Util.Hashtable ht = <span color="#0000ff">new</span> Java.Util.Hashtable();<p><span color="#006600">// 创建一些纯.NET 实例 </span><span color="#0000ff">object</span> obj1 = <span color="#0000ff">new</span> MyDotNetClass();</p><br></br><span color="#0000ff">object</span> obj2 = <span color="#0000ff">new</span> MyDotNetClass2( 7, 9 );<br></br> PureDotNetType obj3 = <span color="#0000ff">new</span> PureDotNetType();<br></br> PureDotNetType2 obj4 = <span color="#0000ff">new</span> PureDotNetType2();<p> obj3.CharProperty = 'B';</p><p><span color="#006600">// 这两个值将在我们的哈希表中得到对象返回值后被消除 </span> obj4.NotUsed = 511;</p><br></br> obj4.AlsoNotUsed = <span color="#660033">"test"</span>;<p><span color="#006600">// 这个值将会被保留,但是在 Java 代码中将会以另外一个名称出现 </span> obj4.OnlyUsedField = 512;</p><p><span color="#006600">// 将.NET 实例放入 Java 哈希表,<br></br> // 请注意这里没有可用的 Java 原始类型提供给.NET 类型,<br></br> // .NET 对象状态被拷贝到通用的 Java 实例之中。<br></br></span> ht.Put( <span color="#660033">"obj1"</span>, obj1 );</p><br></br> ht.Put( <span color="#660033">"obj2"</span>, obj2 );<br></br> ht.Put( <span color="#660033">"obj3"</span>, obj3 );<br></br> ht.Put( <span color="#660033">"obj4"</span>, obj4 );<p><span color="#006600">// 这是一个真实的测试!<br></br> // 现在我们尝试去得到最初的.NET 信息。</span><span color="#0000ff">object</span> o1 = ht.Get( <span color="#660033">"obj1"</span> );</p><br></br><span color="#006699">Console</span>.WriteLine( <span color="#660033">"o1={0}"</span>, o1.ToString());<p><span color="#0000ff">object</span> o2 = ht.Get( <span color="#660033">"obj2"</span> );</p><br></br><span color="#006699">Console</span>.WriteLine( <span color="#660033">"o2={0}"</span>, o2.ToString());<p><span color="#0000ff">object</span> o3 = ht.Get( <span color="#660033">"obj3"</span> );</p><br></br><span color="#006699">Console</span>.WriteLine( <span color="#660033">"o3={0}"</span>, o3.ToString());<p><span color="#0000ff">object</span> o4 = ht.Get( <span color="#660033">"obj4"</span> );</p><br></br><span color="#006699">Console</span>.WriteLine( <span color="#660033">"o4={0}"</span>, o4.ToString());<p><span color="#006699">Console</span>.WriteLine( <span color="#660033">"ht={0}"</span>, ht.ToString() );</p><br></br> }<br></br><span color="#0000ff">catch</span>( JuggerNETFrameworkException jnfe )<br></br> {<br></br><span color="#006699">Console</span>.WriteLine( <span color="#660033">"Exception caught: {0}\n{1}\n{2}"</span>, jnfe.GetType().Name,<br></br> jnfe.Message, jnfe.StackTrace );<br></br> }<br></br> }<br></br> }总的来说,在上述的程序互操作过程之中,在不考虑单一运行环境的速度优势情况下(在单一过程中的数据移动,远比网络传输中的数据移动速度更快,甚至高于快速比特),程序互操作过程包含以下的一些优点:

  • 集中化。在许多情况下,我们希望特定资源(比方说代码中的数据库序列标识符)只存在于一个且仅此一个进程之中,来避免复杂的进程间代码同步的实现。
  • 可靠性。较少的硬件相关性,以及整个系统单一的硬件损耗,使得系统很少会有受到攻击的可能性。
  • 结构化要求。在某些情况下,现有的结构化模型将要求所有程序处理过程替代已有的处理过程,比如说,应用程序的现有用户接口如果使用 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)

查看英文原文: In-process Interoperability - - - - - -

译者简介:高昂,IEEE-CS、CCF 会员,关注开源软件发展与进步,Java GIS 开源项目 uDIG 参与者。目前在资源与环境信息系统国家重点实验室从事网格 GIS、空间数据库研究工作。个人站点为开源网格GIS 试验田。与InfoQ 中文站分享内容,请邮件至 china-editorial@infoq.com

2007-06-01 21:451730
用户头像

发布了 74 篇内容, 共 12.1 次阅读, 收获喜欢 3 次。

关注

评论

发布
暂无评论
发现更多内容

如何学习一项新技术?

平凡人生

计算机接口技术复习题(1-6章)

乌龟哥哥

8月月更

Docker 已运行端口映射怎么破

CTO技术共享

【数据结构实践】手把手带你实现 Python 自定义数组

迷彩

开源 数据结构 面向对象 数组操作 8月月更

【CSS】字体样式,包括字体系列、大小、修饰、粗细、简写...

翼同学

CSS 编程语言 8月月更

Docker下Prometheus和Grafana三部曲之二:细说Docker编排

程序员欣宸

Grafana Prometheus 8月月更

云原生(二十五) | Kubernetes篇之Kubernetes(k8s)临时存储

Lansonli

云原生 k8s 8月月更

数字馆藏电子商务平台开发:如何发展数字藏品?

开源直播系统源码

软件开发 区块链技术 NFT 数字藏品 数字藏品开发

Dockerfile 定制专属镜像

CTO技术共享

面试中常用消息中间件对比

浅羽技术

kafka RocketMQ 消息中间件 Rabbit MQ 8月月更

【Java·访问修饰符】:default、public、protected、private

翼同学

Java 学习 编程语言 8月月更

数字化转型别着急,先看看如何打通数据孤岛吧

雨果

数字化转型 打通数据孤岛

Http缓存原来如此,牛波牛波

知识浅谈

HTTP缓存 8月月更

java的可变参数

TimeFriends

8月月更

Linux 黑客命令装逼小助手

CTO技术共享

开源一夏 | Python 并发编程之死锁

宇宙之一粟

Python 开源 并发编程 死锁 8月月更

头脑风暴:最长回文子序列

HelloWorld杰少

LeetCode 8月月更

精细化资产管理

IT资讯搬运工

【JavaScript】:有关js类型转换的那些事...

翼同学

JavaScript 编程语言、 8月月更 学习分享

未来,人人都是创造者

石云升

开源 未来技术趋势 8月月更

《 合 成 大 西 瓜 》 重 制 版 !( 联 机 版 在 做 了 )

HullQin

CSS JavaScript html 前端 8月月更

开源一夏 | Qiankun框架对于微前端的解耦和沙盒与实战探索心得

恒山其若陋兮

开源 8月月更

开源一夏 | Python 对象的序列和反序列化

宇宙之一粟

Python 开源 序列化 8月月更

豆瓣 TOP3 的 Python 书,千万别错过

图灵教育

iofod——WeUI基础组件解析

独来独往

前端 低代码 开发工具 weui iofod

倒计时1天!计算巢软件免费试用中心发布,即刻报名!

阿里云弹性计算

计算巢 试用中心

Python 教程之输入输出(9)—— print() 中的 sep 参数

海拥(haiyong.site)

Python 8月月更

leetcode 594. Longest Harmonious Subsequence 最长和谐子序列(简单).md

okokabcd

LeetCode 算法与数据结构

Linux热潮下,来呆猫云工作站玩转云上部署Linux工作流新思路

科技怪咖

Java并发面试常见考点

浅羽技术

Java 面试 线程 并发 8月月更

解决在 Spring Boot 中运行 JUnit 测试遇到的 NoSuchMethodError 错误

HoneyMoose

互操作现在进行时_Java_Ted Neward_InfoQ精选文章