熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> JSP教程 >> 正文

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

2013-11-15 09:49:46  來源: JSP教程 

  並非所有的開發者都清楚時下最流行的兩個程序運行環境(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 包含工作流WPF和InfoCard等廣受關注的特性或是在NET過程中使用Java虛擬機提供的工具比如說部署Java語言編寫的那些包含復雜業務邏輯的Spring組件或者實現通過ASPNET訪問JMS消息隊列這樣的功能

  加載動態鏈接庫以及與底層代碼托管環境進行交互是解決互操作問題所面臨的兩個不同問題然而每一項操作都為之提供了標准的應用程序接口來完成這樣的功能舉例來說下面列出的非托管C++代碼來自於Java本地接口JNI的官方文檔目的是利用標准過程(相關的代碼句柄在JNIHosting子目錄裡以InProcInterop方案的一部分存在構建它的最好方法是在命令行裡用指向JDK 目錄位置的JAVA_HOME環境變量來操作
)創建基於Java虛擬機的函數調用

   #include stdafxh
#include

int _tmain(int argc _TCHAR* argv[])
{
     JavaVM *jvm;       /* 表示一個Java虛擬機 */
     JNIEnv *env;       /* 指向本地方法調用接口 */
     JavaVMInitArgs vm_args; /* JDK或JRE 的虛擬機初始化參數 */

     JavaVMOption options[]; int n = ;
     options[n++]optionString = Djavaclasspath=;

     vm_argsversion = JNI_VERSION__;
     vm_argsnOptions = n;
     vm_argsoptions = options;
     vm_argsignoreUnrecognized = false;

     /* 加載或初始化Java虛擬機返回Java本地調用接口
      * 指向變量 env */
     JNI_CreateJavaVM(&jvm (void**)&env &vm_args); // 傳入C++所需的參數

     /* 使用Java本地接口調用 Maintest 方法 */
     jclass cls = env>FindClass(Main);
     jmethodID mid = env>GetStaticMethodID(cls test (I)V);
     env>CallStaticVoidMethod(cls mid );

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

     return ;
}

  在編譯上述代碼時Java開發工具包JDK中的include和include\win目錄將被添加在C++程序的include路徑中並且JDK中lib目錄下的jvmlib必須位於目標代碼連接器的路徑之中程序運行時默認情況下程序的主類Mainclass作為程序執行的入口類與上述文件位於相同的目錄之中並且保證Java運行環境JRE中的jvmdll動態鏈接庫存在一般來說這個動態鏈接庫是存在於系統環境變量的PATH路徑之中(jvmdll通常不需要手動添加在PATH路徑中因為javaexe將會動態的查找jvmdll動態鏈接庫的位置並在找到鏈接庫後記錄下它的位置

  同樣NET通用語言運行時CLR提供自有的應用程序調用接口作為本地API接口來實現同樣的功能代碼如下


   #include stdafxh
#include

int _tmain(int argc _TCHAR* argv[])
{
ICLRRuntimeHost* pCLR = (ICLRRuntimeHost*);
HRESULT hr = CorBindToRuntimeEx(NULL Lwks
STARTUP_CONCURRENT_GC CLSID_CLRRuntimeHost IID_ICLRRuntimeHost
(PVOID*)&pCLR);
if (FAILED(hr))
return ;

hr = pCLR>Start();
if (FAILED(hr))
return ;

DWORD retval = ;
hr = pCLR>ExecuteInDefaultAppDomain(LHelloWorldexe LHello LMain NULL &retval);
if (FAILED(hr))
return ;

hr = pCLR>Stop();
if (FAILED(hr))
return ;

return (int)retval;
}

 如同Java本地接口JNI的示例一樣上面的示例假定應用程序HelloWorldexe在執行時與NET編譯(這兒因為我們期望正用到的(ExecuteInDefaultAppDomain)這個特殊的宿主API能有一個調用它裡面Hello的類這個類要有一個名為Main的方法以將一個字符串當作聲明處理並返回整數值注意這和傳統的C#或者VBNET的入口通道有所不同)都位於當前目錄之下由於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 中提供的C++/CLI或Visual Studio 提供的C++托管代碼來進行代碼編譯的過程

  在這個步驟中復雜之處在於程序運行時需要確保Java虛擬機得到訪問動態鏈接庫的路徑這項工作可以分為兩部分來完成首先當Java類函數的本地方法被程序加載時需要詢問Java虛擬機是否通過RuntimeloadLibrary()操作來請求加載共享庫函數值得注意的是本地類庫請求是在沒有指定文件拓展名的情況下完成這樣的操作不指定拓展名是因為不同的操作系統往往使用不同的約定來共享類庫所以只需指定共享類庫名稱即可比如在Windows操作系統下共享類庫具有DLL後綴然而在Unix或Linux操作系統之下共享類庫常用的約定是使用類似於libNAMEso這樣的名稱就這方面來講Java虛擬機首先需要在特定的操作系統中查詢共享類庫的約定慣例在Windows操作系統之下針對於加載類庫的LoadLibrary()函數官方文檔中有明確的API接口說明但所需的類庫通常都包含在操作系統的安裝目錄中(在Windows操作系統中即為C:\WINDOWS 和 C:\WINDOWS\SYSTEM目錄)或是當前的工作目錄或者已經包含在環境變量PATH的設定之中對於Java虛擬機的類庫調用也需要在其他兩個目錄中查找即在由javalibrarypath系統參數指定的目錄中或是JRE運行環境所在目錄的lib\i路徑之下通常來說推薦使用的方法是在自定義屬性javalibrarypath中指定本地代碼執行參數(在Java虛擬機啟動的時候可以設置好系統參數的路徑)或者指定在JRE運行環境的i目錄中在這個特定的例子中很容易想象的到指定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()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是JavaC++代理工具的NET版本)生成的代碼那樣它提供了兩個功能可以與NET完善集成的Java本地接口調用API使其可以更方便的使用NET環境創建Java應用程序並且提供NET代碼生成器產生NET的代理程序用來配置必須的參數並且執行Java對象中定義的函數方法這樣使用JuggerNET在NET應用中加載JVM程序的示例代碼應該符合下面的過程


     /*
  * Copyright by Codemesh Inc  ALL RIGHTS RESERVED
  */
using System;
using CodemeshJuggerNET;

//
// 下面的代碼設定JVM環境並且在程序中進行Java調用
//
// 使用的Java虛擬機由平台依賴的業務邏輯決定
// 在這個例子中也可以使用JvmPath屬性來設置程序將要使用的JVM
public class Application
{
public static void Main( string[] argv )
{
try
{
//
// 下面的代碼提供了訪問一個對象的途徑你可以使用這個對象來初始化運行時設置
//
IJvmLoader loader = JvmLoaderGetJvmLoader();

//
// 配置Java設置
//

// 設置classpath參數為當前的工作目錄
loaderClassPath = ;

// 在classpath中添加CWD的父目錄
loaderAppendToClassPath( );

// 設置堆棧的最大值
loaderMaximumHeapSizeInMB = ;

//  設置一組 D 選項
loaderDashDOption[ myprop ] = myvalue;
loaderDashDOption[ prop_without_value ] = null;

// 指定 TraceFile記錄文件如果不指定所有的記錄輸出將會加入到 stderr標准錯誤之中
loaderTraceFile = \\tracelog;

//
// 你可以將這一項置空在第一個代理操作執行時或是可以精確加載Java虛擬機的時候
// 使用配置設置來去除程序對於JVM環境的需求
// 如果有錯誤發生將會拋出一個異常
//
loaderLoad();

}
catch( SystemException )
{
ConsoleWriteLine( !!!!!!!!!!!!!!!  we caught an exception  !!!!!!!!!!!!!!!! );
}

  ConsoleWriteLine( ***************  were leaving Main() **************** );

  return;
}
}

  NET到Java代碼生成的代理機制中具備一定的編程技巧因為存在一些手動設置來指定哪一個Java類和包應該被設為代理實現這樣的過程可以使用JuggerNET的GUI工具來指定描述包和類清單的模型文件或者可以使用Ant腳本(這意味著一部分或全部的NET程序發布需要使用Java的Ant工具來實現對於互操作項目來說這並非是完全不切合實際的)通過使用


    /*
  * Copyright by Codemesh Inc  ALL RIGHTS RESERVED
  */

using System;
using CodemeshJuggerNET;
using JavaLang;
using JavaUtil;

///


/// 使用NET類型來定義數據成員
/// 通過拓展序列化的代理接口我們自動為NET類型產生被稱為peer的參數
/// 序列化接口在代碼生成器中進行標記
/// 並且使用Java同等的類型來保持NET實例的序列化信息
///
public class MyDotNetClass : JavaIoSerializable
{
  public int  field = ;
  public int  field = ;
  public string strField = ;

  public MyDotNetClass()
  {
  }

  public MyDotNetClass( int f int f string s )
  {
   field = f;
   field = f;
   strField = s;
  }

  public override string ToString()
  {
   return MyDotNetClass[field= + field + field= + field + strField= + strField + ];
  }
}

///
/// 另一個NET的類型繼承自Serializable
/// 但是聲明為不同類型的數據元素
///
public class MyDotNetClass : JavaIoSerializable
{
  public int[] test = new int[] { };

  public MyDotNetClass()
  {
  }

  public MyDotNetClass( int f int f )
  {
   test[ ] = f;
   test[ ] = f;
  }

  public override string ToString()
  {
   SystemTextStringBuilder result = new SystemTextStringBuilder();

    resultAppend( MyDotNetClass[test=[ );
   for (int i = ; i < testLength; i++)
   {
    if( i != )
     resultAppend( );
    resultAppend( + test[i] );

   }
   resultAppend( ]] );

   return resultToString();  }
}

///
/// 這個類型闡明了如何實現等同序列化的目標
/// 通過為NET類型添加JavaPeer屬性
/// 創建相似的用法來繼承JavaIoSerializable
/// 但是有些不很方便的地方是在需要使用Serializable的時候
/// 在PureDotNetType處不能使用生成的實例
/// JavaPeer屬性列出了兩個不同的屬性
/// 分別是PeerType 和 PeerMarshaller
/// 第一個屬性指定保持數據的Java類型
/// 第二個屬性指定如何序列化NET實例來生成Java實例及其逆過程

///
[JavaPeer(PeerType= demeshpeerSerializablePeer
           PeerMarshaller= CodemeshJuggerNETReflectionPeerValueMarshaller)]
public class PureDotNetType
{
  private char ch = a;
     ///
  /// 一個字段的設置來幫助我們闡明從Java中讀出的實際信息

  ///
  public char CharProperty
  {
   set { ch = value; }
  }

  public override string ToString()
  {
   return PureDotNetType[ch= + ch + ];
  }
}

///
/// 類型闡明了控制同等序列化細節的字段屬性

///
[JavaPeer(PeerType=demeshpeerSerializablePeer
           PeerMarshaller=CodemeshJuggerNETReflectionPeerValueMarshaller)]
public class PureDotNetType
{
  ///
  /// 在去除編組之後的字段值將一直保持是因為它的值沒有被序列化或反序列化

  ///
  [NonSerialized]
  public int    NotUsed = ;

  ///
  /// 在去除編組之後的字段值將一直保持是空值因為它的值沒有被序列化或反序列化

  ///
  [JavaPeer(Ignore=true)]  public string   AlsoNotUsed = null;

  ///
  /// 這個字段的值經過序列化或反序列化
  /// 但是對於Java這個字段是歸類在CustomFieldName之下
  /// 你可能通常不會關心Java的名稱但是如果Java程序可以訪問peer對象
  /// 並且需要訪問自己的數據則可以對其加以關注
  ///
  [JavaPeer(Name=CustomFieldName)]
  public int    OnlyUsedField = ;

  public override string ToString()
  {
   return PureDotNetType[NotUsed= + NotUsed +
                 AlsoNotUsed= + ( AlsoNotUsed == null ? null : AlsoNotUsed ) +
                 OnlyUsedField= + OnlyUsedField + ];
  }
}

public class Peer
{  public static void Main( string[] args )
  {
   try
   {
    IJvmLoader loader = JvmLoaderGetJvmLoader();

    if( argsLength > && args[ ]Equals( info) )
     ;//loaderPrintLdLibraryPathAndExit();

    // 生成哈希表的實例
    JavaUtilHashtable ht = new JavaUtilHashtable();

    // 創建一些純NET實例
    object   obj = new MyDotNetClass();
    object   obj = new MyDotNetClass( );
    PureDotNetType obj = new PureDotNetType();
    PureDotNetType obj = new PureDotNetType();

    objCharProperty = B;

    // 這兩個值將在我們的哈希表中得到對象返回值後被消除
    objNotUsed = ;
    objAlsoNotUsed = test;
    // 這個值將會被保留但是在Java代碼中將會以另外一個名稱出現
    objOnlyUsedField = ;

    // 將NET 實例放入Java哈希表
    // 請注意這裡沒有可用的Java原始類型提供給NET類型
    // NET對象狀態被拷貝到通用的Java實例之中

    htPut( obj obj );
    htPut( obj obj );
    htPut( obj obj );
    htPut( obj obj );

    // 這是一個真實的測試!
    // 現在我們嘗試去得到最初的NET信息
    object o = htGet( obj );
    ConsoleWriteLine( o={} oToString());

    object o = htGet( obj );
    ConsoleWriteLine( o={} oToString());

    object o = htGet( obj );
    ConsoleWriteLine( o={} oToString());

    object o = htGet( obj );
    ConsoleWriteLine( o={} oToString());

    ConsoleWriteLine( ht={} htToString() );
   }
   catch( JuggerNETFrameworkException jnfe )
   {
    ConsoleWriteLine( Exception caught: {}\n{}\n{} jnfeGetType()Name
                                      jnfeMessage jnfeStackTrace );
   }
  }
}

  總的來說在上述的程序互操作過程之中在不考慮單一運行環境的速度優勢情況下(在單一過程中的數據移動遠比網絡傳輸中的數據移動速度更快甚至高於快速比特)程序互操作過程包含以下的一些優點

  集中化在許多情況下我們希望特定資源(比方說代碼中的數據庫序列標識符)只存在於一個且僅此一個進程之中來避免復雜的進程間代碼同步的實現

  可靠性較少的硬件相關性以及整個系統單一的硬件損耗使得系統很少會有受到攻擊的可能性

  結構化要求在某些情況下現有的結構化模型將要求所有程序處理過程替代已有的處理過程比如說應用程序的現有用戶接口如果使用ASPNET編寫並且應用程序部分的互操作性用以實現為EJB消息驅動Bean在JMS消息隊列中的消息傳送處理過程則在本地程序中傳送消息給Java服務並且僅是釋放消息到JMS隊列之中這樣的過程就顯得有些多余特別是在假定JMS客戶端代碼非常簡潔的時候程序實現代價較高將JMS的客戶端代碼放入ASPNET進程之中(Codemesh為JuggerNET代理實現JMS消息客戶端提供了特別的版本)來實現與現有程序架構保持一致的簡潔途徑

  此外並非是所有的互操作解決方案都將通過inproc方法來實現但其中一些會使用這樣的方法並且開發者無需害怕這樣的想法即便是提供這些操作的工具有著非常大的使用價值

關於作者

  Ted Neward是大規模企業應用系統方面的獨立咨詢人也是JavaNET和XML服務相關主題的會議上的演講人致力於Java與NET的互操作技術在Java與NET方面他曾撰寫過幾本廣受認可的書籍其中包括最近出版的《高效企業級Java開發》一書

資源

  The Java Native Interface (Liang)

  Java Native Interface (Gordon)

  The JNI page at the Java SE website ()

  Customizing the Common Language Runtime (Pratschner)

  Shared Source CLI (Stutz Neward Shilling)

  The C++/CLI Language Specification (ECMA International)


From:http://tw.wingwit.com/Article/program/Java/JSP/201311/19187.html
  • 上一篇文章:

  • 下一篇文章:
  • Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.