OceaBase开发者大会落地上海!4月20日共同探索数据库前沿趋势!报名戳 了解详情
写点什么

互操作现在进行时

  • 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:451626
用户头像

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

关注

评论

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

DevOps 的道术法器,探寻 DevOps “立体化”实践之旅

CODING DevOps

DevOps 软件工程 CODING DevOps

什么是微信朋友圈广告?朋友圈广告怎么投放?怎么收费的?

吴老师讲业

互联网广告 广告业 微信朋友圈 朋友圈

一文掌握 Go 并发模式 Context 上下文

陈明勇

Go golang 后端 Context 上下文

融云跨平台 SDK 自动生成技术的探索和实践

融云 RongCloud

sdk 融云 办公效率 通讯 图片资源

如何用一行代码实现监测 OpenAI,大幅提升使用体验

Yestodorrow

可观测性 用户体验 应用性能 ChatGPT

开源7天Github斩获4.5万Stars!阿里2023版高并发设计实录鲨疯了

做梦都在改BUG

Java 架构 微服务 系统设计 高并发

LP流动性挖矿/算力系统开发源码搭建

Congge420

区块链 系统开发 云算力挖矿系统开发详解 云算力模式系统开发源码

AE插件 真实地图路径绘制GEOlayers 3 Mac v1.5.7

真大的脸盆

Mac Mac 软件 AE插件 地图路径插件

在研制带处理器的电子产品时,如何提高抗干扰能力和电磁兼容性?

华秋PCB

电磁 电路 处理器 控制器 抗干扰

火山引擎DataTester分享:A/B实验中常见的8个错误

字节跳动数据平台

量化交易系统开发——现货策略

薇電13242772558

量化策略

软件测试丨Python学习笔记之内置库科学计算、日期与时间处理

测试人

Python 软件测试 测试开发

吉林省网络安全等级测评机构有哪些?在哪里?

行云管家

网络安全 等级保护 吉林

ChatGPT系统开发AI人功智能方案

Congge420

AI Gallery ChatGPT 人工智能ChatGPT 吗?

NFT元宇宙链游系统开发逻辑分析

Congge420

区块链 NFT链游 元宇宙链游

知识拷问:工作站和服务器哪个更适合做CST电磁仿真?

思茂信息

仿真软件 cst cst使用教程 cst电磁仿真 cst仿真软件

Makefile常用命令详解

小万哥

c++ 程序员 面试 后端 makefile

城市开发者平台:程序员成长和创新的“家园”

华为云开发者联盟

云计算 华为云 华为云开发者联盟 企业号 5 月 PK 榜

数字城市发展下的技术趋势,你了解多少?

没有用户名丶

Python从0到1丨图像增强的顶帽运算和底帽运算

华为云开发者联盟

Python 人工智能 华为云 华为云开发者联盟 企业号 5 月 PK 榜

【源码分析】【netty】FastThreadLocal为什么快?

如果晴天

源码分析 Netty 多线程 并发 netty

厚积薄发|迭代为什么叫冲刺?

CODING DevOps

DevOps 敏捷 软件工程

软件测试/测试开发丨Python学习笔记之基本数据类型与操作

测试人

Python 软件测试 测试开发 数据类型

行业分析| 快对讲-融合会议的应用

anyRTC开发者

音视频 视频会议 快对讲 融合会议 电话会议

架构实战-毕业设计

程序员小张

「架构实战营」

实践「容器镜像扫描」,Get 云原生应用的正确打开方式

极狐GitLab

Docker DevOps 云原生 DevSecOps 容器镜像

火山引擎DataLeap:3步打造“指标管理”体系,幸福里数据中心是这么做的

字节跳动数据平台

大数据 字节跳动 数据管理 指标管理 数据研发

IPP Swap孵化器/LP挖矿系统开发方案

Congge420

区块链 ipfs挖矿用什么app

SaaS 产品如何选择设计协作工具?

CODING DevOps

敏捷 设计 SaaS CoDesign 团队协作工具

教你使用Feign替换RestTemplate

做梦都在改BUG

车载手势识别技术:未来交通的革命性解决方案

来自四九城儿

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