IKVM是Microsoft .NET Framework和Mono平台上的一個Java實現,他包括以下一些部分:
1. 一個用.NET實現的Java虛擬機器
2. Java類庫的.NET實現
3. 一些用於Java和.NET之間互操作的工具集
IKVM提供2種主要的方式在.NET平台上運用Java項目。一種是動態方式,即通過IKVM .NET實現的JVM直接運行Java項目,這種方式需要動態將Java的class或者jar等檔案編譯成.NET的CIL,
所以啟動的時候會比較慢。另外一種是靜態方式,即使用IKVM的工具先將class、jar檔案編譯為.NET的CIL程式集,然後在.NET framework上運行這些程式集,這種方式可以在.NET開發
中直接使用Java的類庫
項目組件、程式集介紹參考這裡:
1. ikvm.exe,基本對應於java.exe的功能,用於動態運行方式
2. ikvmc.exe,基本對應於javac.exe的功能,用於靜態方式
3. ikvmstub.exe,為.NET程式集產生Java class的代理檔案,以便在Java中使用.NET程式集的功能
這個項目目前仍然非常活躍,從04年開始到現在一直在開發更新中。對於純粹基於JDK的Java項目,IKVM目前應該能夠處理的比較完善了,
對於使用了Swing和JNI的Java項目,IKVM也有提供支援,但不瞭解支援程度以及穩定性等方面
IKVM對於2中狀況比較有用: 一種Java中的一些項目沒有.NET實現的,另一種是某些Java開源項目存在相應的.NET port,但Java的項目很活躍一直在完善更新,而.NET port則停滯了
項目使用的lisence值得考慮,IKVM.OpenJDK.*.dll使用GPL V2,其他的IKVM專案檔使用的lisence應該是BSD之類的
下面拿StringTemplate做個測試
要求安裝有JDK,在Windows環境變數中設定好JAVA_HOME路徑(JDK目錄),PATH中包含JDK的bin目錄
下載安裝IKVM,這個步驟非常簡單,只需要從IKVM下載最新的bin distribution,
解壓到一個目錄,將IKVM的bin目錄添加到系統內容變數PATH中
1. StringTemplate的Java測試專案
從StringTemplate下載最新的Java StringTemplate。如果下載source進行編譯的話,可以使用Ant編譯(項目中有build.xml檔案),
如果使用Eclipse,除了添加antlr的jar引用外,還需要使用antlr產生詞法解析器、文法解析器、token等相關的java檔案(在target\generated-sources目錄下有已經產生好的java檔案),
放入到src\org\antlr\stringtemplate\language目錄下。編譯項目後用Eclipse匯出stringtemplate的jar檔案
我用的版本為antlr-2.7.7.jar、stringtemplate-3.2.1.jar
用Eclipse建立測試專案,添加antlr-2.7.7.jar、stringtemplate-3.2.1.jar的引用,測試用的java檔案如下:
Contact.java
public class Contact { private String _name; private String _email; private int _score;
public Contact(String name, String email, int score){ this._name=name; this._email=email; this._score=score; } public String getName(){ return this._name; } public String getEMail(){ return this._email; } public int getScore(){ return this._score; } /* * StringTemplate不支援在模板中做條件判斷,因此下面在model中用屬性實現 */ public Boolean getIsLevel3(){ return this._score>1000; } public Boolean getIsLevel2(){ return this._score>200 && this._score<=1000; } public Boolean getIsLevel1(){ return this._score<=200; }}
sttest.java
import java.util.Map;import java.util.HashMap;import java.util.ArrayList;import java.util.List;import org.antlr.stringtemplate.*;import org.antlr.stringtemplate.language.*;
public class sttest { public static void main(String[] args) { StringTemplateGroup group = new StringTemplateGroup("st-test" , "E:\\Richie\\java\\workspace\\StringTemplateTest\\lib\\st" , DefaultTemplateLexer.class); StringTemplate st = group.lookupTemplate("contact_list"); // a simple attribute st.setAttribute("simple_attribute", "Hello StringTemplate!"); // an object with properties as an attribute Contact c = new Contact("Richie", "Richie-test@gmail.com", 100); st.setAttribute("contact", c); // collections test for Map Map<String, Integer> map = new HashMap<String, Integer>(); map.put("key1", 111); map.put("key2", 222); map.put("key3", 333); st.setAttribute("items_hashtable", map); // collections test for Array int[] array = new int[3]; array[0]=1999; array[1]=2888; array[2]=3777; st.setAttribute("items_array", array); // collections test for List List<Contact> list = new ArrayList<Contact>(); list.add(new Contact("Jacky Pan", "JackyPan-test@gmail.com", 1608)); list.add(new Contact("RicCC", "RicCC-test@gmail.com", 180)); list.add(new Contact("Richie", "Richie-test@gmail.com", 682)); st.setAttribute("items_list", list);
System.out.println(st.toString()); }}
直接用Eclipse將其編譯為Contact.class、sttest.class檔案
測試用的StringTemplate檔案如下:
contact_list.st
a simple attribute: $simple_attribute$$!this is a comment!$a contact instance:{ Name="$contact.Name$", EMail="$contact.EMail$" }Items - Map:$! Remarks: The following ST syntax for maps (.NET IDictionary implementations) is not supported by StringTemplate C# port (Version 3.2)!$map.item_name: { key1 - $items_hashtable.key1$ },{ key3 - $items_hashtable.key3$ }iterator: $items_hashtable.keys: {{ $attr$ - $items_hashtable.(attr)$ }};separator=","$Items - Array:{ $items_array: {value|$value$};separator=","$ }Items - List:$items_list: row(seq="odd"), row(seq="even")$
row.st
<tr class="list-$seq$"> <td>$attr.Name$</td> <td>$attr.EMail$</td> <td>$attr.score$</td> <td>$if(attr.IsLevel3)$Diamond$elseif(attr.IsLevel2)$Golden$else$Standard$endif$</td></tr>$\n$$! Remarks: the previous instruction will yield a line feed !$
為了方便,建立一個IKVMTest的目錄,將antlr-2.7.7.jar、stringtemplate-3.2.1.jar、Contact.class、sttest.class檔案都拷貝到IKVMTest目錄中,
在IKVMTest中建立st目錄,放入contact_list.st、row.st檔案
在命令列進入IKVMTest目錄,使用java sttest運行測試專案,結果如下:
StringTemplate C#的實現不太完善,上面java的測試專案中就有2個文法,目前StringTemplate C#的3.2版本不支援(或者是bug),
一個是對Map元素的訪問方式(即$map_entry.(key)$文法),另一個是$elseif$指令,這個指令在不少情況下會出錯。正好借用IKVM項目做這個測試
2. 動態方式運行Java項目
在命令列進入IKVMTest目錄,使用ikvm sttest運行測試專案,結果與使用java.exe運行完全一樣。也可以看到,ikvm sttest啟動時有段時間CPU佔用比較高,
此時IKVM在將相關class、jar檔案動態編譯為CIL代碼
ikvm.exe的用法參考這裡
3. 靜態方式運行Java項目
首先,將antlr-2.7.7.jar轉為.NET程式集
ikvmc -out:antlr-2.7.7.dll -target:library -platform:x86 antlr-2.7.7.jar
然後將stringtemplate-3.2.1.jar轉為.NET程式集。因為StringTemplate引用了antlr,所以下面的命令必須使用-reference指定antlr的dll檔案
ikvmc -out:stringtemplate-3.2.1.dll -target:library -platform:x86 -reference:antlr-2.7.7.dll stringtemplate-3.2.1.jar
最後,將Contact.class、sttest.class轉為.NET的exe檔案sttest.exe
ikvmc -out:sttest.exe -target:exe -platform:x86 -reference:antlr-2.7.7.dll -reference:stringtemplate-3.2.1.dll -main:sttest Contact.class sttest.class
上面的步驟會在我們測試目錄中產生3個檔案: antlr-2.7.7.dll、stringtemplate-3.2.1.dll、sttest.exe,這就是從jar、class轉換過來的.NET託管程式集了
採用靜態方式運行從java轉換過來的項目時,需要引用IKVM相關的一些dll檔案,我們可以將IKVM的dll註冊到全域的GAC中,也可以把相關dll檔案拷貝到我們的測試目錄
這裡我們直接從IKVM的bin目錄,將IKVM.OpenJDK.Beans.dll、IKVM.OpenJDK.Charsets.dll、IKVM.OpenJDK.Core.dll、IKVM.OpenJDK.Security.dll、IKVM.OpenJDK.Text.dll、
IKVM.OpenJDK.Util.dll、IKVM.Runtime.dll這幾個dll拷貝到我們的測試目錄IKVMTest中(我猜測試專案只用到了這些dll),然後在命令列運行sttest.exe,可以看到運行結果與
前面java.exe的運行結果完全一致
ikvmc.exe的用法參考這裡
4. 在.NET中使用Java項目
接下來我們用Visual Studio建立一個.NET的測試專案,來測試在.NET下面直接使用從Java轉換過來的StringTemplate
我們手工把java的測試專案port成C#的實現。用Visual Studio建立一個IKVM.Test的Console項目,引用步驟 [3. 靜態方式運行Java項目] 中產生的antlr-2.7.7.dll、
stringtemplate-3.2.1.dll這2個檔案,引用IKVM.OpenJDK.Core.dll檔案,項目代碼如下:
Contact.cs
namespace IKVM.Test{ using System; public class Contact { private String _name; private String _email; private int _score;
public Contact(String name, String email, int score) { this._name = name; this._email = email; this._score = score; } public String getName() { return this._name; } public String getEMail() { return this._email; } public int getScore() { return this._score; } //StringTemplate不支援在模板中做條件判斷,因此下面在model中用屬性實現 public Boolean getIsLevel3() { return this._score > 1000; } public Boolean getIsLevel2() { return this._score > 200 && this._score <= 1000; } public Boolean getIsLevel1() { return this._score <= 200; } }}
Program.cs
namespace IKVM.Test{ using System; using org.antlr.stringtemplate; using org.antlr.stringtemplate.language; class Program { static void Main(string[] args) { StringTemplateGroup group = new StringTemplateGroup("st-test" , ".\\st", typeof(DefaultTemplateLexer)); StringTemplate st = group.lookupTemplate("contact_list"); // a simple attribute st.setAttribute("simple_attribute", "Hello StringTemplate!"); // an object with properties as an attribute Contact c = new Contact("Richie", "Richie-test@gmail.com", 100); st.setAttribute("contact", c); // collections test for Map java.util.Map map = new java.util.HashMap(); map.put("key1", 111); map.put("key2", 222); map.put("key3", 333); st.setAttribute("items_hashtable", map); // collections test for Array int[] array = new int[3]; array[0] = 1999; array[1] = 2888; array[2] = 3777; st.setAttribute("items_array", array); // collections test for List java.util.List list = new java.util.ArrayList(); list.add(new Contact("Jacky Pan", "JackyPan-test@gmail.com", 1608)); list.add(new Contact("RicCC", "RicCC-test@gmail.com", 180)); list.add(new Contact("Richie", "Richie-test@gmail.com", 682)); st.setAttribute("items_list", list); Console.Write(st.toString()); Console.ReadKey(); } }}
編譯後,將IKVM.Test.exe拷貝到我們的測試目錄IKVMTest,運行結果仍然與前面java.exe啟動並執行結果完全一致。可以看到,用這樣的方式,對StringTemplate C#版本不支援的文法也能正確執行了
通過上面.NET的測試專案我們可以發現以下幾點:
1. 基礎資料型別 (Elementary Data Type)(例如string、int、bool)通過IKVM直接實現互操作
2. 從java轉換過來的CIL項目,在.NET中使用時需要遵循java的約定
例如上面的Contact類,java下的StringTemplate採用反射讀取屬性值,使用的是java下約定,通過調用getName()、getEMail()等方法
讀取,.NET中使用時我們要採用同樣的約定才行,而不能定義成.NET的Property
3. JDK的class libraries需要使用IKVM.OpenJDK.Core等IKVM的實現
如果我們在項目中使用IKVM,可能需要通過一些封裝手段分離client對IKVM的依賴,例如上面對StringTemplate的測試,我們可以通過介面封裝的方法,
將IDictionary轉換成java.util.Map,將IList轉換成java.util.List等,避免用戶端去引用java.util等這些命名空間。某些情況下這個分離可能比較煩
效能對比
效能測試使用的st模板以及給StringTemplate設定屬性的代碼都跟上面的代碼一樣,測試結果為
[Java版本] : [StingTemplate的C#版本] : [4. 在.NET中使用Java項目] = [1.5] : [1] : [16.2]
.NET的StringTemplate實現比java版本的效能好一些,在.NET中使用IKVM靜態方式效能損失比較大。IKVM存在效能損失是肯定的,但這個差距太大了,可能對於不同項目結果會不一樣吧
文章中測試用的專案檔:下載