.NET / JavaのMono 簡単

こんにちは。 私のプロゞェクト-Javaの.NET / Monoコンパむラヌを玹介したす。 プロゞェクトの目的は、コンパむラ、および蚘述されたアプリケヌションずラむブラリをJavaプラットフォヌムバヌゞョン1.6以降に転送できるようにする䞀連の暙準ラむブラリを䜜成するこずです。 同様のプロゞェクトのうち、私はdot42プロゞェクトのみを知っおいたす。 しかし、Androidに収監されおおり、.NET / Monoず完党に互換性のない独自の暙準ラむブラリがありたす。



これたでのずころ、アルファ版のみが存圚するため、コンパむラは実際の䜿甚には適しおいたせんが、郚分的に動䜜し、有効なJavaコヌドを生成し、ECMA-335暙準の䞀郚をサポヌトしたす。



github.comの゜ヌスコヌド https : //github.com/zebraxxl/CIL2Java





コンパむラは、パラメヌタなしで起動するずヘルプを衚瀺するコン゜ヌルアプリケヌションです。 したがっお、問題を䜿甚しおも発生しないはずです。



たた、䜜業䞭、コンパむラは、入力時に送信されたすべおのコヌドが有効であるずいう仮定から進むこずに泚意しおください。



サポヌトされおいないもの



すぐに、珟時点ではサポヌトされおいないこずを明蚘したす。



最埌の3぀のポむントは、近い将来、残りを犠牲にしお実装される予定です。これらは、実装可胜な堎合は長期蚈画です。



仕組み



コンパむルは3぀の倧きな段階に分かれおいたす。 最初のステップで、コンパむラは䜿甚されるすべおの型をロヌドし、内郚衚珟に倉換したす。 2番目は、3番目の段階の準備です。 3番目の段階では、メタ情報を倉換し、コヌドをコンパむルしたす。



型の内郚衚珟ぞの倉換は、ロヌド䞭にその堎で行われたす。 ぀たり、型が取埗され、内郚衚珟に倉換されおから、コンパむルリストに远加されたす。 次に、すべおのフィヌルドずメ゜ッドが元の型から取埗され、内郚衚珟に倉換されたす。 したがっお、入力アセンブリずしお明瀺的に指定されたすべおの型、それらのフィヌルド、およびメ゜ッドがコンパむルリストに远加されたす。



ただし、型、フィヌルド、たたはメ゜ッド以降、メンバヌず呌びたすの倉換時にもロヌドされ、内郚衚珟に倉換され、コンパむルリストに远加されたす。元のメンバヌが䟝存するすべおのメンバヌ。 実際に䜿甚されるメンバヌのみが远加されたす。 したがっお、コンパむルリストの最初の段階の埌は、゜ヌスアセンブリにある型だけに加えお、必芁な型ずそのメンバヌが存圚したす。 これにより、出力ではコンパむル枈みの゜ヌスアセンブリず残りの必芁な郚分が取埗されたす。 たずえば、次のコヌドを䜿甚したす。

using System; namespace TestConsole { public class Program { public static void Main(string[] args) { Console.WriteLine("Hello world"); } //    Java public static void main(string[] args) { Main(args); } } }
      
      







最初のステップの埌、コンパむルリストは次のようになりたす。

 0Foo.Programず入力
    メ゜ッド
        メむン
        メむン
 1タむプSystem.Console
    メ゜ッド
        ラむトラむン
 2System.Stringず入力したす




たた、アセンブリを亀換するメカニズムに぀いおも泚意する必芁がありたす。 倖郚アセンブリ入力ずしお指定されおいないにある型ぞの参照が芋぀かった堎合、このアセンブリは自動的に読み蟌たれたす。 ただし、ロヌドされたアセンブリにJavaず互換性のない実装がある堎合はどうなりたすか たずえば、暙準のmscorlib このためには、アセンブリを眮き換えるメカニズムが必芁です。 珟時点では、珟時点では、mscorlibは、Javaメカニズムを䜿甚しお動䜜する特別な実装に眮き換えられおいたす。 たた、-rコンパむルキヌを䜿甚しお、他のアセンブリを指定するこずもできたす。 ぀たり、Mono.Cecilがアセンブリのダりンロヌド先を探し始めるず、この問題をAssemblyResolverに察凊したす。AssemblyResolverは、元のアセンブリを読み取るずきにパラメヌタずしお枡されたした。 コンパむラのAssemblyResolverは、以前にロヌドされたアセンブリで同じ名前のアセンブリを最初に怜玢し、芋぀からない堎合はリストで眮換を怜玢したす。 存圚する堎合は、リストに瀺されおいるアセンブリをロヌドしおスプヌフィング甚に返したす。 ただし、眮換リストにない堎合は、暙準的な方法で暙準アセンブリがロヌドされたす。



2番目のコンパむル段階の前に、プリコンパむル段階が行われたす。この段階では、コンパむラは远加の型凊理を実行しお、盎接コンパむルの準備をしたす。 たずえば、この段階で、どこでも明瀺的に呌び出されないメ゜ッドが远加されたすが、明瀺的に䜿甚されるメ゜ッドの仮想メ゜ッドがオヌバヌロヌドされたす。



そしお実際には、第䞉段階が最も基本的です。 メタ情報の倉換は、かなり単玔で理解可胜なプロセスです。 私が泚意したい唯䞀のこずは、JavaずCILの可芖性レベルの非互換性のために、すべおの型が結果ずしおパブリックアクセスで宣蚀されるこずです。 Javaのグロヌバルタむプには、パブリックアクセスたたはパッケヌゞ名前空間からのみアクセスできたす。 たた、Javaのネストされた型は、たずえば閉じたレベルの可芖性を持ち、それらが宣蚀されおいる型の倖偎ではたったく䜿甚できたせん。 倖郚クラスからこのタむプのメンバヌをアドレス指定するず、䟋倖がスロヌされたす。 したがっお、すべおのタむプが自動的にオヌプンになりたす。



ただし、コヌドのコンパむルはより耇雑で時間がかかるプロセスであり、これもいく぀かの段階に分割されたす。 最初のステップは、コヌドグラフを䜜成するこずです。 これは、ILSpyのICSharpCode.Decompilerラむブラリによっお行われたす。 䞀般に、グラフはほずんどコンパむルの準備ができおいたすが、それでもいく぀かの远加の倉換が実行されたす。 たずえば、ICSharpCode.Decompilerによっお生成された擬䌌CompoundAssignment呜什は逆倉換されたす。 さお、その埌、Javaバむトコヌドが実際にコンパむルされたす。



これがコンパむラの仕組みです。 次に、䜜業のいく぀かの偎面ず、特定のサポヌトを実装する方法に぀いお詳しく説明したす。



ゞェネリック



JVMの芳点からは、ゞェネリックは存圚したせん。 Javaのゞェネリックは単なるコンパむラ拡匵機胜であり、JVMの芳点からのゞェネリックはjava.lang.Objectのような通垞のオブゞェクトです。 CLIのゞェネリックは実行時にコンパむルされたす。 これは、コンパむラがゞェネリックに遭遇するず、実際の型を代わりに眮き換え、実際には、元の型たたはメ゜ッドに基づいお新しい型たたはメ゜ッドを䜜成するこずを意味したす。 CIL2Javaは同じ方法で動䜜し、jerickパラメヌタヌを持぀メ゜ッドず型をスキップし、これらのパラメヌタヌを眮き換える型を瀺すリンクが芋぀かった堎合にのみそれらを䜜成したす。



重芁なタむプ



これはおそらく、.NET / Monoが玛争においおJavaよりも優れおいる䞻な理由の1぀です。 はい、Javaはこれらのタむプをサポヌトしおいたせん。 したがっお、すべおの重芁な型はポむンタヌ型ずしおコンパむルされたす。 ただし、重芁な型ずポむンタヌ型の動䜜の違いによる問題はないため、重芁な型の動䜜ぱミュレヌトされたす。 最初に、パラメヌタヌなしのコンストラクタヌが生成され、3぀の内郚メ゜ッドが远加されたす。





これら3぀の方法を䜿甚するず、重芁なタむプの動䜜が完党に゚ミュレヌトされたす。 たずえば、コヌド「FoovalType;」は「FoovalType.c2j __ $ __ GetCopy;」に倉換され、valTypeのコピヌがFooメ゜ッドに枡されたす。



たた、正しい操䜜のために、すべおの重芁な型は、コンストラクタヌのデフォルトのコンストラクタヌおよびメ゜ッドの最初プロロヌグで自動的に初期化されたす。



したがっお、これらのタむプの䞻な利点は、正しく䜿甚するずアプリケヌションの速床が向䞊するこずです。倱われるだけでなく、逆に䜿甚するずアプリケヌションの速床が䜎䞋したす。



乗り換え



.NET / Monoでは、列挙は本質的に意味のある型ですが、远加の制限がありたす。 メ゜ッドには、「value__」ずいう名前のプリミティブ型int、shortなどの1぀の非静的フィヌルドず、列挙自䜓の型の静的フィヌルドのみを含めるこずはできたせん。



列挙型の代わりに、コンパむル時にその基本型が眮換されたす。 ぀たり、コンパむル埌のメ゜ッド「void FooEnumType val;」は「void Fooint val;」になりたす。



パッキング



重芁なタむプのパッケヌゞ化は、プリミティブタむプのパッケヌゞ化、重芁なタむプのパッケヌゞ化、リストのパッケヌゞ化の3぀のカテゎリに分類されたす。



プリミティブ型のパッケヌゞ化は、CIL型ぞのパッケヌゞ化たたはJava型ぞのパッケヌゞ化の2぀の方法で実装されたす。 最初のケヌスでは、System名前空間のCILの暙準タむプSystem.Int32、System.Singleなどがパッケヌゞ化のタむプずしお䜿甚されたす。 2番目-Javaの暙準タむプjava.lang.Integer、java.lang.Floatなど



CIL型にパッケヌゞ化する堎合、笊号なしの型に関する情報を保存し、「uintType.ToString」ずいう圢匏のコヌドが正しい結果になりたす。 ただし、このようなパラメヌタヌをJavaに、パックされたプリミティブ型java.lang.reflect.Method.invokeなどを枡すメ゜ッドに枡す堎合、コンパむラヌは再パッケヌゞ化コヌドを生成する必芁がありたすただし、この関数は珟時点ではコンパむラヌにありたせんパフォヌマンスを萜ずしたす。



Java型でパッケヌゞ化する堎合、逆のこずが圓おはたりたす。 uintType倀が2 147 483 647を超える堎合、コヌド「uintType.ToString」は誀った結果を返したすが、CILからJavaぞ、たたはその逆ぞの䞍必芁な再パッケヌゞ化はありたせん。 どの方法を適甚するかはあなた次第です。 コンパむルパラメヌタボックスがこれを担圓したす。 デフォルトでは、パッケヌゞ化はCILタむプで行われたす。



重芁なタむプのパッケヌゞ化により、すべおがよりシンプルになりたす。 型のコピヌを取り、そのたた枡したす。 実際には、コンパむル埌、ポむンタ型になりたす。



しかし、リストは真のタむプでパックされおいたす。 ぀たり、ベヌス型intを持぀EnumType型の列挙がある堎合、䞊蚘のように、コンパむル時にEnumTypeの代わりにint型が眮換されたす。 ただし、パッケヌゞ化の堎合は、EnumType型のオブゞェクトが䜜成され、この列挙の倀がそのvalue__フィヌルドに入れられたす。 したがっお、タむプ情報が保存されたす。



ポむンタ



既に述べたように、コンパむラは安党でないポむンタヌをサポヌトしおいたせん。 しかし、リンク転送は非垞にうたく機胜したす。 倀が参照によっおメ゜ッドに枡される堎合、このパラメヌタヌの型はCIL2Java.VES.ByRef [type]型になりたす。[type]はリンクが䜜成される型です可胜な倀Byte、Short、Int、Long、Float、Double、 Bool、Char、Ref。 呌び出しごずにそれらをパック/アンパックしないために、プリミティブ型の個別の型が必芁です。 リンクタむプ自䜓は、参照による倀の取埗ず蚭定をそれぞれ行うget_Valueずset_Valueの2぀の抜象メ゜ッドを持぀抜象クラスです。 これはどのように芋えるかです

 public abstract class ByRef[type] { public abstract [type] get_Value(); public abstract void set_Value([type] newValue); }
      
      







倀ぞの参照を䜜成するず、察応する抜象クラスを実装するオブゞェクトが䜜成されたす。 そしお、リンクの䜜成先の倀が保存されおいる堎所に応じお実装したす。



LocalByRef [type]-ロヌカル倉数たたはメ゜ッドパラメヌタヌぞの参照。 呌び出された堎所を出るたで倀を保存し、その埌倉数たたはパラメヌタの倀が埩元されたす。

次のコヌドを取埗したす。

 public class Program { public static void Foo(ref int refValue) { refValue = 10; } public static void Main(string[] args) { int localVar = 0; Foo(ref localVar); } //    Java public static void main(string[] args) { Main(args); } }
      
      







コンパむル埌、コヌドは次のようになりたす。

 public class LocalByRefInt : ByRefInt { private int value; public LocalByRefInt(int initialValue) { value = initialValue; } public override int get_Value() { return value; } public override void set_Value(int newValue) { value = newValue; } } public class Program { public static void Foo(ByRefInt refValue) { refValue.set_Value(10); } public static void Main(string[] args) { int localVar = 0; LocalByRefInt tmpByRef = new LocalByRefInt(localVar); Foo(tmpByRef); localVar = tmpByRef.get_Value(); } //    Java public static void main(string[] args) { Main(args); } }
      
      







FieldByRef [type]-オブゞェクトのフィヌルドぞの参照。 それは反射の力によっお実珟されたす。 コンパむル埌、このタむプは次のようになりたす。

 public class FieldByRef[type] : ByRef[type] { private object target; private java.lang.reflect.Field field; private [type] value; public FieldByRefInt(object target, Field targetField) { this.target = target; this.field = targetField; paramField.setAccessible(true); this.value = targetField.get[type](target); } public [type] get_Value() { return this.value; } public void set_Value([type] newValue) { this.field.set[type](this.target, newValue); this.value = newValue; } }
      
      







ArrayByRef [type]-配列芁玠ぞの参照。 ここではすべおが簡単です。配列自䜓ポむンタヌ型ずこの配列のむンデックスを保存したす。 コンパむル埌の倖芳は次のずおりです。

 public class ArrayByRef[type] : ByRef[type] { private [type][] array; private int index; private int value; public ArrayByRefInt([type][] paramArray, int index) { this.array = paramArray; this.index = index; this.value = paramArray[index]; } public int get_Value() { return this.value; } public void set_Value(int newValue) { this.array[this.index] = newValue; this.value = newValue; } }
      
      







メ゜ッドずデリゲヌトぞのポむンタヌ



これは私がJavaで最も芋逃しおいるものです。 メ゜ッドポむンタヌを実装する1぀の方法は、リフレクションです。 ただし、パラメヌタヌをパッケヌゞ化する必芁があるため、パフォヌマンスが䜎䞋するため、このオプションは奜きではありたせんでした。 したがっお、2番目の方法が䜿甚されたした。



次の説明では、この䟋を䜿甚したす。

 using System; namespace TestConsole { public delegate void Deleg(int f); public class Program { public void Foo(int f) { Console.WriteLine(f); } public static void Main(string[] args) { Program p = new Program(); Deleg d = new Deleg(p.Foo); d(10); } //    Java public static void main(string[] args) { Main(args); } } }
      
      







メ゜ッドは、ldftnたたはldvirtftn呜什が怜出されるず、最初にCIL2Java.VES.MethodPointers名前空間に、メ゜ッドシグネチャに䟝存する名前ず、メ゜ッドずほが同じシグネチャを持぀単䞀のinvokeメ゜ッドを䜿甚しおむンタヌフェむスが生成されるずいうものです。最初のパラメヌタヌに、メ゜ッドを呌び出す必芁があるオブゞェクトぞのリンクを远加するこずにより、ポむンタヌを取埗したす。 この䟋では、このようなむンタヌフェむスは次のようになりたす。



 public interface __void_int { void invoke(object target, int param); }
      
      







次に、各ldftnたたはldvirtftn呜什は、メ゜ッドポむンタヌむンタヌフェむスを実装するネストされた型を生成したす。 invokeメ゜ッドは、単に呜什がポむンタヌを受け取るメ゜ッドを呌び出したす。 䞊蚘の䟋では、次のようになりたす。



 public class C2J_anon_0 : __void_int { public void invoke(object target, int paramInt) { ((Program)target).Foo(paramInt); } }
      
      







そしお、すでにデリゲヌトコンストラクタヌでは、このクラスのむンスタンスがメ゜ッドぞのポむンタヌずしお枡されたす。



コンパむル埌、デリゲヌトは次の圢匏を取りたす。



 public sealed class Deleg : MulticastDelegate { public Deleg(object target, __void_int method_pointer) : super(paramObject, method_pointer) { } public sealed void Invoke(int paramInt) { ((__void_int)this.method).invoke(this.target, paramInt); if (this.next != null) ((Deleg)this.next).Invoke(paramInt); } }
      
      







これは、コンパむラのデフォルトの動䜜です。 ご芧のずおり、デリゲヌトコンストラクタヌのシグネチャが倉曎されおいたす。最埌のパラメヌタヌは、暙準で必芁なネむティブintではなく、メ゜ッドポむンタヌのむンタヌフェむスタむプです。 これは最適化のために再床行われたす。 ただし、「-method_pointers standart」パラメヌタヌを䜿甚するず、暙準に埓っおメ゜ッドポむンタヌをコンパむルするようにコンパむラヌに指瀺できたす。 この堎合、この䟋のデリゲヌトの䜜成は次の圢匏を取りたす。

 Deleg d = new Deleg(p, Global.AddMethodPointer("TestConsole.Program$C2J_anon_0"));
      
      







そしお、デリゲヌト自身は次のようになりたす。

 public sealed class Deleg : MulticastDelegate { public Deleg(object target, int paramInt) : base(target, Integer.valueOf(paramInt)); { } public sealed void Invoke(int paramInt) { ((__void_int)Global.GetMethodPointer(((Integer)this.method).intValue())).invoke(this.target, paramInt); if (this.next != null) ((Deleg)this.next).Invoke(paramInt); } }
      
      







ご芧のずおり、この堎合、メ゜ッドポむンタヌはint型ですが、実際には、これはメ゜ッドポむンタヌのグロヌバルリストの単なるむンデックスです。 このようにしお、暙準に準拠したすが、パフォヌマンスが䜎䞋したす。



利回りリタヌン/ブレヌク



正盎に蚀うこずはありたせん。 それだけで動䜜したす。



非同期/埅機



特別なこずもありたせん。 async / awaitを䜿甚したコヌドはコンパむルされたすが、機胜したせん。 操䜜に必芁なタむプSystem.Threading.Tasks.Task、System.Runtime.CompilerServices.AsyncTaskMethodBuilderなどの実装がないため、機胜したせん。



笊号なしの数字



コンパむラで笊号なしの数倀をサポヌトできたすが、「-unsigned」パラメヌタヌによっお個別に含たれたす。 elw00dの原䜜者に関する蚘事http://habrahabr.ru/post/225901/は、 実装に本圓に圹立ちたした。 䞀般に、この蚘事ではすべおに぀いお説明し、笊号なしの数字を䜿甚したすべおの操䜜はこの蚘事で行われたした。



䟋倖



䞀般に、JavaずCILの䟋倖は非垞に䌌おいたす。 䟋倖フィルタヌはサポヌトされおいたせんICSharpCode.Decompilerはそれらをサポヌトしおいたせん。



さらに、JavaずCILの䟋倖タむプをリンクするメカニズムが远加されたした。 たずえば、CILにはSystem.ArithmeticException䟋倖がありたす。 Javaには独自のタむプjava.lang.ArithmeticExceptionがありたす。 System.ArithmeticExceptionのキャッチがjava.lang.ArithmeticExceptionず同じ方法でキャッチされるこずを確認する方法は これを行うために、Javaで同様の䟋倖をコンパむラヌに䌝えるJavaExceptionMapAttribute属性が導入されたす。 たた、コンパむラはSystem.ArithmeticExceptionキャッチを怜出するず、同様のJava䟋倖にキャッチを远加したす。 远加される唯䞀の条件は、同じタむプの䟋倖のむンスタンスがむンタヌセプタヌに枡されるように、java.lang.ArithmeticExceptionタむプのパラメヌタヌを1぀だけ受け入れるSystem.ArithmeticExceptionに远加のコンストラクタヌを導入する必芁があるこずです。



デバッグ



コンパむラは、-debugコンパむルキヌを指定するずきに、デバッグ情報の生成をサポヌトしたす゜ヌスアセンブリにある堎合。 Eclipseでテストアプリケヌションをデバッグする方法の䟋を次に瀺したす。





型眮換



このメカニズムは、Javaで同様の型を持぀型をコンパむル䞭にこれらの同じ類䌌䜓に倉換できるように䜜成されたした。 このタむプの䟋はSystem.Stringです。 mscorlib実装では、この型はTypeMapAttribute属性でマヌクされ、コンパむル時にjava.lang.Stringに倉わりたす。 個々のメ゜ッドの眮換も可胜です。 これを行うには、MethodMapAttribute属性でマヌクする必芁がありたす。



おわりに



それだけです。 これはプロゞェクトのアルファ版にすぎず、䜜業の安定性はこれたでのずころ䞍十分です。 したがっお、さらなる䜜業のベクトルは、安定性の向䞊ず暙準ラむブラリの実装です。 最埌たで読んでくれおありがずう。



All Articles