当前位置:首页 > 全部子站 > IT > 思科认证

Java和.NET互操作究竟有什么用

来源:长理培训发布时间:2017-12-23 14:48:15

 百度广告

 并非所有的开发者都清楚,时下最流行的两个程序运行环境(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;
   vm_args.options = options;

   /* 加载或初始化Java虚拟机,返回Java本地调用接口
   JNI_CreateJavaVM(&jvm, (**)&env, &vm_args); // 传入C++所需的参数

   /* 使用Java本地接口调用 Main.test 方法 */
FindClass("Main");
GetStaticMethodID(cls, "test", "(I)V");
CallStaticVoidMethod(cls, mid, 100);

   /* 完成工作 */
DestroyJavaVM();

   

 

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


 

"stdafx.h"
_tmain( argc, _TCHAR* argv)
ICLRRuntimeHost* pCLR = (ICLRRuntimeHost*)0;
STARTUP_CONCURRENT_GC, CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost,
(FAILED(hr))

hr = pCLR->Start();
-1;

DWORD retval = 0;
ExecuteInDefaultAppDomain(L"HelloWorld.exe", L"Hello", L"Main", NULL, &retval);
-1;

hr = pCLR->Stop();
-1;

()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;

//
//
// 在这个例子中,也可以使用JvmPath属性来设置程序将要使用的JVM。
{
{

{
// 下面的代码提供了访问一个对象的途径,你可以使用这个对象来初始化运行时设置。
IJvmLoader loader = JvmLoader.GetJvmLoader();

//--------------------------------------------------------------------
//

// 设置classpath参数为当前的工作目录

// 在classpath中添加CWD的父目录

// 设置堆栈的最大值

//  设置一组 -D 选项
loader.DashDOption[ "prop_without_value" ] = ;

// 指定 TraceFile记录文件.如果不指定,所有的记录输出将会加入到 stderr标准错误之中

//--------------------------------------------------------------------
// 使用配置设置来去除程序对于JVM环境的需求。
//

}
{
}

  Console.WriteLine( "***************  we're leaving Main() ****************" );

  
}

 

||| 
 .NET到Java代码生成的代理机制中,具备一定的编程技巧,因为存在一些手动设置来指定哪一个Java类和包应该被设为代理,实现这样的过程可以使用JuggerNET的GUI工具来指定描述包和类清单的模型文件,或者可以使用Ant脚本(这意味着一部分或全部的.NET程序发布需要使用Java的Ant工具来实现,对于互操作项目来说,这并非是完全不切合实际的),通过使用"


 

 /*
  */

System;
Java.Lang;

///


/// 通过拓展序列化的代理接口,我们自动为.NET类型产生被称为"peer"的参数。
/// 并且使用Java同等的类型来保持.NET实例的序列化信息。
MyDotNetClass : Java.Io.Serializable
    field1 = 0;
  strField = "";

  MyDotNetClass()
  }

  MyDotNetClass( f1, f2, s )
   field1 = f1;
   strField = s;

  ToString()
   "MyDotNetClass[field1=" + field1 + ", field2=" + field2 + ", strField='" + strField + "']";
}

///


/// 但是声明为不同类型的数据元素。
MyDotNetClass2 : Java.Io.Serializable
  test = { 0, 1, 2 };

  MyDotNetClass2()
  }

  MyDotNetClass2( f1, f2 )
   test[ 0 ] = f1;
  }

  ToString()
   System.Text.StringBuilder result = System.Text.StringBuilder();

   result.Append( "MyDotNetClass2[test=[" );
   {
   result.Append( "," );

   }

   result.ToString();  }

///


/// 通过为.NET类型添加JavaPeer属性。
/// 但是有些不很方便的地方是,在需要使用Serializable的时候,
/// JavaPeer属性列出了两个不同的属性:
/// 第一个属性指定保持数据的Java类型,
/// 
     PeerMarshaller= "Codemesh.JuggerNET.ReflectionPeerValueMarshaller")]
{
   /// 

  /// 
  {
  }

  ToString()
   "PureDotNetType[ch='" + ch + "']";
}

/// 

/// 
     PeerMarshaller="Codemesh.JuggerNET.ReflectionPeerValueMarshaller")]
{

  /// 
     NotUsed = 42;

  ///


  /// 

  ///


  /// 但是对于Java,这个字段是归类在'CustomFieldName'之下。
  /// 并且需要访问自己的数据,则可以对其加以关注。
  [JavaPeer(Name="CustomFieldName")]
  ToString()
   "PureDotNetType2[NotUsed=" + NotUsed +
        ", OnlyUsedField=" + OnlyUsedField + "]";
}

Peer
  {
   {

   ( args.Length > 1 && args[ 0 ].Equals( "-info") )

   // 生成哈希表的实例

   // 创建一些纯.NET实例
      obj2 = MyDotNetClass2( 7, 9 );
   PureDotNetType2 obj4 = PureDotNetType2();

   obj3.CharProperty = 'B';

   // 这两个值将在我们的哈希表中得到对象返回值后被消除
   obj4.AlsoNotUsed = "test";
   obj4.OnlyUsedField = 512;

   // 将.NET 实例放入Java哈希表,
   // .NET对象状态被拷贝到通用的Java实例之中。
   ht.Put( "obj1", obj1 );
   ht.Put( "obj3", obj3 );

   // 这是一个真实的测试!
   o1 = ht.Get( "obj1" );

   o2 = ht.Get( "obj2" );

   o3 = ht.Get( "obj3" );

   o4 = ht.Get( "obj4" );

   Console.WriteLine( "ht={0}", ht.ToString() );
   ( JuggerNETFrameworkException jnfe )
   Console.WriteLine( "Exception caught: {0}"n{1}"n{2}", jnfe.GetType().Name,
   }
}

 

||| 
 总的来说,在上述的程序互操作过程之中,在不考虑单一运行环境的速度优势情况下(在单一过程中的数据移动,远比网络传输中的数据移动速度更快,甚至高于快速比特),程序互操作过程包含以下的一些优点:

集中化。在许多情况下,我们希望特定资源(比方说代码中的数据库序列标识符)只存在于一个且仅此一个进程之中,来避免复杂的进程间代码同步的实现。

可靠性。较少的硬件相关性,以及整个系统单一的硬件损耗,使得系统很少会有受到攻击的可能性。

结构化要求。在某些情况下,现有的结构化模型将要求所有程序处理过程替代已有的处理过程,比如说,应用程序的现有用户接口如果使用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)

责编:罗莉

发表评论(共0条评论)
请自觉遵守互联网相关政策法规,评论内容只代表网友观点,发表审核后显示!

国家电网校园招聘考试直播课程通关班

  • 讲师:刘萍萍 / 谢楠
  • 课时:160h
  • 价格 4580

特色双名师解密新课程高频考点,送国家电网教材讲义,助力一次通关

配套通关班送国网在线题库一套

课程专业名称
讲师
课时
查看课程

国家电网招聘考试录播视频课程

  • 讲师:崔莹莹 / 刘萍萍
  • 课时:180h
  • 价格 3580

特色解密新课程高频考点,免费学习,助力一次通关

配套全套国网视频课程免费学习

课程专业名称
讲师
课时
查看课程
在线题库
面授课程更多>>
图书商城更多>>
在线报名
  • 报考专业:
    *(必填)
  • 姓名:
    *(必填)
  • 手机号码:
    *(必填)